Skip to content

refactor: always preserve marketplace search state in URL#160

Open
tomerqodo wants to merge 2 commits intoqodo_claude_vs_qodo_base_refactor_always_preserve_marketplace_search_state_in_url_pr9from
qodo_claude_vs_qodo_head_refactor_always_preserve_marketplace_search_state_in_url_pr9
Open

refactor: always preserve marketplace search state in URL#160
tomerqodo wants to merge 2 commits intoqodo_claude_vs_qodo_base_refactor_always_preserve_marketplace_search_state_in_url_pr9from
qodo_claude_vs_qodo_head_refactor_always_preserve_marketplace_search_state_in_url_pr9

Conversation

@tomerqodo
Copy link
Copy Markdown

Benchmark PR from agentic-review-benchmarks#9

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Simplify marketplace search state to always use URL parameters

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Simplify marketplace search state management by always using URL query parameters
• Remove conditional logic for preserving search state in atoms
• Eliminate hydration component and atom-based state management
• Fix search mode logic to correctly check category collections
Diagram
flowchart LR
  A["Atom-based State Management"] -->|Remove| B["URL Query Parameters Only"]
  C["HydrateMarketplaceAtoms Component"] -->|Delete| B
  D["Conditional preserveSearchStateInQuery Logic"] -->|Simplify| B
  E["Search Mode Logic"] -->|Fix negation| B
Loading

Grey Divider

File Changes

1. web/app/components/plugins/marketplace/atoms.ts ✨ Enhancement +4/-23

Always use URL query state for marketplace search

• Remove preserveSearchStateInQueryAtom and related atom definitions
• Simplify useSearchPluginText(), useActivePluginType(), and useFilterPluginTags() to always
 use useQueryState
• Change useActivePluginType() to use 'tab' query parameter instead of 'category'
• Fix search mode logic by removing negation operator in PLUGIN_CATEGORY_WITH_COLLECTIONS.has()
 check

web/app/components/plugins/marketplace/atoms.ts


2. web/app/components/plugins/marketplace/hydration-client.tsx Miscellaneous +0/-15

Remove hydration client component

• Delete entire file containing HydrateMarketplaceAtoms component
• Remove atom hydration logic that conditionally set search state preservation

web/app/components/plugins/marketplace/hydration-client.tsx


3. web/app/components/plugins/marketplace/index.tsx ✨ Enhancement +8/-12

Remove hydration wrapper and simplify structure

• Remove import of HydrateMarketplaceAtoms component
• Remove HydrateMarketplaceAtoms wrapper from component tree
• Update JSDoc comment to remove mention of preserving search params in URL
• Simplify component structure by removing conditional atom hydration

web/app/components/plugins/marketplace/index.tsx


View more (1)
4. web/app/components/plugins/plugin-page/context.tsx Miscellaneous +1/-1

Relax selector parameter type annotation

• Change usePluginPageContext selector parameter type from specific function signature to any
• Simplify type annotation for better flexibility

web/app/components/plugins/plugin-page/context.tsx


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Mar 10, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (1) 📎 Requirement gaps (0)

Grey Divider


Action required

1. usePluginPageContext uses any 📘 Rule violation ✓ Correctness
Description
usePluginPageContext now types selector as any, which defeats TypeScript type safety and can
hide incorrect selector usage. This violates the requirement to avoid explicit any in TypeScript
production code.
Code

web/app/components/plugins/plugin-page/context.tsx[51]

+export function usePluginPageContext(selector: any) {
Evidence
PR Compliance ID 8 forbids explicit any types; the changed function signature explicitly uses
any for the selector parameter.

AGENTS.md
web/app/components/plugins/plugin-page/context.tsx[51-51]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`usePluginPageContext` accepts `selector: any`, which bypasses type checking and violates the no-explicit-any rule.

## Issue Context
This hook wraps `useContextSelector` and can be typed generically so callers get proper inference and safety.

## Fix Focus Areas
- web/app/components/plugins/plugin-page/context.tsx[51-53]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Search mode default inverted 🐞 Bug ✓ Correctness
Description
useMarketplaceSearchMode now defaults to search mode when the active category has collections,
which forces plugin search queries (often with empty query) and prevents showing collections for
categories that do have collections. This contradicts the comment and the plugin-type-switch
behavior, causing the marketplace to render the wrong view by default.
Code

web/app/components/plugins/marketplace/atoms.ts[R40-44]

  const searchMode = useAtomValue(searchModeAtom)
  const isSearchMode = !!searchPluginText
    || filterPluginTags.length > 0
-    || (searchMode ?? (!PLUGIN_CATEGORY_WITH_COLLECTIONS.has(activePluginType)))
+    || (searchMode ?? (PLUGIN_CATEGORY_WITH_COLLECTIONS.has(activePluginType)))
  return isSearchMode
Evidence
The code comment explicitly says categories without collections should be forced into search mode,
but the new boolean uses has(...) instead of !has(...). Downstream, useMarketplaceData uses
isSearchMode to decide whether to fetch search results (plugins) or show collections, so this
inversion flips the main UI mode selection.

web/app/components/plugins/marketplace/atoms.ts[29-45]
web/app/components/plugins/marketplace/constants.ts[25-30]
web/app/components/plugins/marketplace/plugin-type-switch.tsx[89-94]
web/app/components/plugins/marketplace/state.ts[20-35]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`useMarketplaceSearchMode` uses the wrong default boolean for categories with/without collections, causing the marketplace to enter search mode by default for `all/tool` (which have collections) and not enter search mode for categories that lack collections.

### Issue Context
The comment and related code (collections prefetching + plugin type switch behavior) indicate that categories *without* collections should force search mode.

### Fix Focus Areas
- web/app/components/plugins/marketplace/atoms.ts[35-45]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Tab param collides globally 🐞 Bug ✓ Correctness
Description
Marketplace category state is now stored in the generic URL parameter tab, which is also used by
the globally-mounted account settings modal query state. This causes URL-state interference:
opening/switching account settings tabs can overwrite marketplace category, and switching
marketplace categories can overwrite the account settings tab value.
Code

web/app/components/plugins/marketplace/atoms.ts[R22-24]

export function useActivePluginType() {
-  const preserveSearchStateInQuery = useAtomValue(preserveSearchStateInQueryAtom)
-  const queryState = useQueryState('category', marketplaceSearchParamsParsers.category)
-  const atomState = useAtom(activePluginTypeAtom)
-  return preserveSearchStateInQuery ? queryState : atomState
+  return useQueryState('tab', marketplaceSearchParamsParsers.category)
}
Evidence
Marketplace writes its category to ?tab=..., while the global ModalContextProvider manages
account settings modal state using useAccountSettingModal, which stores the account tab in the
same tab query key. Because ModalContextProvider is mounted in the common layout (wrapping
/plugins), both systems are active on the same pages and will compete for the same query
parameter.

web/app/components/plugins/marketplace/atoms.ts[22-24]
web/hooks/use-query-params.ts[64-73]
web/context/modal-context.tsx[156-168]
web/app/(commonLayout)/layout.tsx[23-37]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Marketplace currently stores its active category in `?tab=...`, which conflicts with the global account settings modal’s `tab` query parameter.

### Issue Context
`ModalContextProvider` is mounted in the common layout and uses `useAccountSettingModal`, which reads/writes `tab`.

### Fix Focus Areas
- web/app/components/plugins/marketplace/atoms.ts[22-27]
- web/app/components/plugins/marketplace/search-params.ts[5-9]
- web/app/components/plugins/marketplace/hydration-server.tsx[12-22]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Server/client param mismatch 🐞 Bug ⛯ Reliability
Description
Server-side hydration parses the marketplace category from the category query param, but the
client now reads/writes the category using tab. As a result, server prefetch/hydration will ignore
the client’s actual category and can prefetch the wrong data (or none), degrading correctness and
performance when searchParams is supplied.
Code

web/app/components/plugins/marketplace/atoms.ts[R22-24]

export function useActivePluginType() {
-  const preserveSearchStateInQuery = useAtomValue(preserveSearchStateInQueryAtom)
-  const queryState = useQueryState('category', marketplaceSearchParamsParsers.category)
-  const atomState = useAtom(activePluginTypeAtom)
-  return preserveSearchStateInQuery ? queryState : atomState
+  return useQueryState('tab', marketplaceSearchParamsParsers.category)
}
Evidence
The shared parser object defines a category key, and the server loader
(createLoader(marketplaceSearchParamsParsers)) reads params.category to decide prefetch
behavior. After the PR, the client no longer uses category in the URL for active plugin type, so
server-side parsing cannot see the actual category selection.

web/app/components/plugins/marketplace/search-params.ts[5-9]
web/app/components/plugins/marketplace/hydration-server.tsx[12-21]
web/app/components/plugins/marketplace/atoms.ts[22-24]
web/app/components/plugins/marketplace/index.tsx[8-15]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Server-side parsing/hydration reads `category`, but client-side marketplace state now uses `tab`, so SSR prefetching and client state are desynchronized.

### Issue Context
`HydrateQueryClient` still uses `createLoader(marketplaceSearchParamsParsers)` and checks `params.category`.

### Fix Focus Areas
- web/app/components/plugins/marketplace/atoms.ts[22-24]
- web/app/components/plugins/marketplace/search-params.ts[5-9]
- web/app/components/plugins/marketplace/hydration-server.tsx[12-29]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

}

export function usePluginPageContext(selector: (value: PluginPageContextValue) => any) {
export function usePluginPageContext(selector: any) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. usepluginpagecontext uses any 📘 Rule violation ✓ Correctness

usePluginPageContext now types selector as any, which defeats TypeScript type safety and can
hide incorrect selector usage. This violates the requirement to avoid explicit any in TypeScript
production code.
Agent Prompt
## Issue description
`usePluginPageContext` accepts `selector: any`, which bypasses type checking and violates the no-explicit-any rule.

## Issue Context
This hook wraps `useContextSelector` and can be typed generically so callers get proper inference and safety.

## Fix Focus Areas
- web/app/components/plugins/plugin-page/context.tsx[51-53]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 40 to 44
const searchMode = useAtomValue(searchModeAtom)
const isSearchMode = !!searchPluginText
|| filterPluginTags.length > 0
|| (searchMode ?? (!PLUGIN_CATEGORY_WITH_COLLECTIONS.has(activePluginType)))
|| (searchMode ?? (PLUGIN_CATEGORY_WITH_COLLECTIONS.has(activePluginType)))
return isSearchMode
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Search mode default inverted 🐞 Bug ✓ Correctness

useMarketplaceSearchMode now defaults to search mode when the active category has collections,
which forces plugin search queries (often with empty query) and prevents showing collections for
categories that do have collections. This contradicts the comment and the plugin-type-switch
behavior, causing the marketplace to render the wrong view by default.
Agent Prompt
### Issue description
`useMarketplaceSearchMode` uses the wrong default boolean for categories with/without collections, causing the marketplace to enter search mode by default for `all/tool` (which have collections) and not enter search mode for categories that lack collections.

### Issue Context
The comment and related code (collections prefetching + plugin type switch behavior) indicate that categories *without* collections should force search mode.

### Fix Focus Areas
- web/app/components/plugins/marketplace/atoms.ts[35-45]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 22 to 24
export function useActivePluginType() {
const preserveSearchStateInQuery = useAtomValue(preserveSearchStateInQueryAtom)
const queryState = useQueryState('category', marketplaceSearchParamsParsers.category)
const atomState = useAtom(activePluginTypeAtom)
return preserveSearchStateInQuery ? queryState : atomState
return useQueryState('tab', marketplaceSearchParamsParsers.category)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

3. Tab param collides globally 🐞 Bug ✓ Correctness

Marketplace category state is now stored in the generic URL parameter tab, which is also used by
the globally-mounted account settings modal query state. This causes URL-state interference:
opening/switching account settings tabs can overwrite marketplace category, and switching
marketplace categories can overwrite the account settings tab value.
Agent Prompt
### Issue description
Marketplace currently stores its active category in `?tab=...`, which conflicts with the global account settings modal’s `tab` query parameter.

### Issue Context
`ModalContextProvider` is mounted in the common layout and uses `useAccountSettingModal`, which reads/writes `tab`.

### Fix Focus Areas
- web/app/components/plugins/marketplace/atoms.ts[22-27]
- web/app/components/plugins/marketplace/search-params.ts[5-9]
- web/app/components/plugins/marketplace/hydration-server.tsx[12-22]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 22 to 24
export function useActivePluginType() {
const preserveSearchStateInQuery = useAtomValue(preserveSearchStateInQueryAtom)
const queryState = useQueryState('category', marketplaceSearchParamsParsers.category)
const atomState = useAtom(activePluginTypeAtom)
return preserveSearchStateInQuery ? queryState : atomState
return useQueryState('tab', marketplaceSearchParamsParsers.category)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

4. Server/client param mismatch 🐞 Bug ⛯ Reliability

Server-side hydration parses the marketplace category from the category query param, but the
client now reads/writes the category using tab. As a result, server prefetch/hydration will ignore
the client’s actual category and can prefetch the wrong data (or none), degrading correctness and
performance when searchParams is supplied.
Agent Prompt
### Issue description
Server-side parsing/hydration reads `category`, but client-side marketplace state now uses `tab`, so SSR prefetching and client state are desynchronized.

### Issue Context
`HydrateQueryClient` still uses `createLoader(marketplaceSearchParamsParsers)` and checks `params.category`.

### Fix Focus Areas
- web/app/components/plugins/marketplace/atoms.ts[22-24]
- web/app/components/plugins/marketplace/search-params.ts[5-9]
- web/app/components/plugins/marketplace/hydration-server.tsx[12-29]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants