Three independent defects made catalog search feel unstable.
Stale results flash on every search. `isSearchMode` was derived from the raw
nuqs `q` while the request body used the 500ms-debounced value, so keystroke #1
flipped into search mode with an empty query. That fired a full empty-query
search whose generic top-plugins list rendered for the debounce window before
the real results replaced it. Both now read the same debounced value.
Grid unmount on every query change. The infinite query had no
`placeholderData`, so `data` went undefined whenever the key changed and
ListWrapper's `!isLoading` gate tore the whole result grid out, collapsing the
container and jumping the scroll position. Measured against a live catalog, a
sort change blanked the grid for 152 of 360 sampled frames; with
`keepPreviousData` and an `isRefreshing` dim it is 0 of 461.
Requests that never settle. The Marketplace oRPC link called `globalThis.fetch`
with no deadline, so a stalled connection left the query pending forever with
no error state and no retry — the reported permanent spinner. Requests now
carry a 15s deadline composed with (never replacing) react-query's abort
signal. The server prefetch and the banner fetch get their own 2.5s budget:
awaiting them unbounded held the whole RSC response, pushing time-to-first-byte
to ~7s against a 3s-delayed API while the browser sat on the previous page.
Supporting changes:
- `getMarketplacePlugins` and `getMarketplaceCollectionsAndPlugins` no longer
swallow every rejection into a successful empty page. That turned outages
and aborted keystrokes into "no plugins found", suppressed retries, cached
the emptiness for the full staleTime, and killed `getNextPageParam`
permanently via `total: 0`. ListWrapper now renders a retry affordance.
- The collections fan-out is bounded to 4 concurrent requests instead of
firing one per collection at once, and per-collection failures degrade to an
empty carousel rather than a blank catalog.
- Scroll pagination measures once per frame instead of per scroll event. Each
threshold hit calls `fetchNextPage`, which defaults to `cancelRefetch: true`,
so an unthrottled burst aborted and restarted the in-flight page request —
and the backend counts those aborts against its search circuit breaker.
- Cross-page results are deduped by `org/name`; Meilisearch resolves
`install_count` ties by document order and the sync task rewrites those
documents every minute, so pages can overlap and collide on React keys.
- Search queries no longer inherit `retry: 3`; ~7s of backoff reads as a hang.
Align the 41px hero grid to the same 50% origin as the six icons so
the 1px strokes sit flush with each tile. Match Figma Ellipse 5 for
the title/search wash, and size the hero to four grid rows so the
bottom icons are not clipped.
Stack homepage banners, search, and catalog to the 879px Figma mobile
frame on the independent marketplace site only. Event and ad slides use
the 357px mobile art frame; blog and recommend stay stacked 197+160.
The standalone and embedded creator pages call marketplaceClient.creatorDetail,
but those routes were dropped from the marketplace contract. Restore them, skip
avatar fetches when none was uploaded, and add creatorProfile strings to every
plugin locale.
Use Autocomplete Portal/Positioner/Popup after the dify-ui split, restore
creator-profile selection, and move the language filter search to InputGroup.
Count a frame impression after the current slide stays 50% visible for 1s, and emit Amplitude click/item_click events that match the banner export schema.
Keep the suggestion popup closed until results settle so the standalone
site does not flash the missing gotoAnything.searching key. Render
plugin and template icons from the marketplace asset URLs.
Prefer the current marketplace origin so marketplace.dify.dev opens creators.dify.dev instead of inheriting a baked-in .ai URL.
Co-authored-by: Cursor <cursoragent@cursor.com>
The public marketplace owns the suggestion dropdown. Console plugin and
template search keep updating results as the query changes.
Co-authored-by: Cursor <cursoragent@cursor.com>
Compose the Plugins and Templates catalog homes from one HomeShell so the
page scaffold cannot drift, split the templates page into category
navigation, pagination and link helpers, and hand the collection list
pre-filtered locale data instead of filtering twice. Replace the
hand-rolled banner payload guards with a zod schema keeping the same
lenient semantics, share the partner-collection constants, label the
plugin type filter group as categories, and backfill the missing Lao
trending title.
Co-authored-by: Cursor <cursoragent@cursor.com>
Break the trending banner into navigation and slide modules with shared
home constants, hoist the marketplace container id and partner-collection
constants for reuse across the catalog, and drop the i18n keys the
redesigned home no longer renders.
Co-authored-by: Cursor <cursoragent@cursor.com>
The autoplay plugin skips its own setup when a carousel has one page, so
the eager play() path dereferenced an undefined delay list and crashed
the standalone marketplace templates tab. Keep autoplay stopped for
single-page carousels and pause rotation permanently once focus enters,
dropping the play/pause toggle that resumed it.
Co-authored-by: Cursor <cursoragent@cursor.com>
CI type-checks without the Next-generated image declarations, so *.webp
needs an explicit module declaration like the other formats. Also keep
getCarouselItemsPerPage module-private since only the hook consumes it,
which unblocks the knip production check.
Co-authored-by: Cursor <cursoragent@cursor.com>
- resample the 3MB trending background to a 97KB webp and serve a frozen
recommend banner from the E2E stub so the benchmark covers the carousel
- show the on-page filtered count in template search and redirect
out-of-range pages to the last available page
- localize the carousel controls across all 24 locales and replace the
i18n type casts with typed selectors
- surface Marketplace API failures with a retry action instead of
rendering them as empty results
- use iconify marks for the hero decorations, share the carousel
page-size hook to avoid a hydration mismatch, move banner types into
@dify/contracts, scope the Cmd+K shortcut to the standalone platform,
point the embedded logo at /marketplace, pass resolvedTheme to the
detail dialogs, and reveal the detail iframe after a loading timeout
Co-authored-by: Cursor <cursoragent@cursor.com>
home-header.tsx is a server component on the standalone marketplace
homepage, where calling buttonVariants() from the client-marked
dify-ui button module crashes SSR with a 500. Extract the Creator
Center link into its own 'use client' file, mirroring home-guide.tsx.
Co-authored-by: Cursor <cursoragent@cursor.com>
The knip production check flagged these as unused, but the outer
dify-marketplace repository imports them via path aliases into its own
Next.js app, so deleting them broke that build. Restore them and
declare them as knip entries with the cross-repo consumer documented.
Co-authored-by: Cursor <cursoragent@cursor.com>
knip production flagged web/app/components/plugins/marketplace/index.tsx
and home/marketplace-live-search.tsx as unused after the homepage page
composed EmbeddedMarketplace directly; drop them with their specs. Raise
LONG_TASK_BUDGET_MS to 300ms since hydration peaks ~220ms on shared CI
runners under 4x CPU throttling.
Co-authored-by: Cursor <cursoragent@cursor.com>
- move the marketplace performance E2E lane out of main CI (opt-in workflow_dispatch)
- add server-rendered pagination for template search and validate page/view/sort params at the route boundary
- match template locale filtering by language prefix with an explicit English fallback
- cache the template collection fan-out (TTL + in-flight dedupe), lower per-collection limit and batch requests
- reset the forced search mode atom when the plugin catalog mounts
- clear stale search suggestions during the debounce window
- let the trending Play control override focus/hover pause reasons
- honor prefers-reduced-motion on the eagerly playing first-collection carousel
- give the Creator Center link an accessible, localized name
- replace the nested main landmark on the templates page with a div
- localize banner read-more actions and backfill marketplace.home keys across all 24 locales
Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve the review comments on the marketplace homepage: restore list SSR
hydration with an eager first collection, make the loading detail iframe
inert, drop stale search suggestions, add carousel pause controls and
off-screen a11y handling, widen the trending pause boundary, bind initial
banners to the server locale, and remove nested interactive links plus dead
CSS scan config. Refactor the marketplace silent-authorize hook to be
self-contained, extract shared OAuth URL helpers, and delete the unused
allowedAbsoluteOrigins option. Serve the performance benchmark from a frozen
local marketplace API stub and gate it with a dedicated Chromium CI lane.
Co-authored-by: Cursor <cursoragent@cursor.com>
Stop exporting unused marketplace helpers and remove the leftover
Trigger plugin icon. Assert the card icon through its img src instead
of a background-image style the marketplace variant no longer uses.