diff --git a/src/i18n/locales/ca/marketplace.json b/src/i18n/locales/ca/marketplace.json index 6c64374447..cf0710ac97 100644 --- a/src/i18n/locales/ca/marketplace.json +++ b/src/i18n/locales/ca/marketplace.json @@ -38,6 +38,12 @@ "noResults": "No s'han trobat etiquetes.", "selected": "Mostrant elements amb qualsevol de les etiquetes seleccionades" }, + "installed": { + "label": "Filtra per estat", + "all": "Tots els articles", + "installed": "Instal·lats", + "notInstalled": "No instal·lats" + }, "title": "Marketplace" }, "done": "Fet", diff --git a/src/i18n/locales/de/marketplace.json b/src/i18n/locales/de/marketplace.json index 2981441cf0..dea82aa44c 100644 --- a/src/i18n/locales/de/marketplace.json +++ b/src/i18n/locales/de/marketplace.json @@ -38,6 +38,12 @@ "noResults": "Keine Tags gefunden.", "selected": "Zeige Elemente mit einem der ausgewählten Tags" }, + "installed": { + "label": "Nach Status filtern", + "all": "Alle Artikel", + "installed": "Installierte", + "notInstalled": "Nicht installiert" + }, "title": "Marketplace" }, "done": "Fertig", diff --git a/src/i18n/locales/en/marketplace.json b/src/i18n/locales/en/marketplace.json index f141aaeb05..17ae20078a 100644 --- a/src/i18n/locales/en/marketplace.json +++ b/src/i18n/locales/en/marketplace.json @@ -38,6 +38,12 @@ "noResults": "No tags found.", "selected": "Showing items with any of the selected tags" }, + "installed": { + "label": "Filter by status", + "all": "All Items", + "installed": "Installed", + "notInstalled": "Not Installed" + }, "title": "Marketplace" }, "done": "Done", diff --git a/src/i18n/locales/es/marketplace.json b/src/i18n/locales/es/marketplace.json index e12e1d1dc1..ecee7c1645 100644 --- a/src/i18n/locales/es/marketplace.json +++ b/src/i18n/locales/es/marketplace.json @@ -38,6 +38,12 @@ "noResults": "No se encontraron etiquetas.", "selected": "Mostrando elementos con cualquiera de las etiquetas seleccionadas" }, + "installed": { + "label": "Filtrar por estado", + "all": "Todos los artículos", + "installed": "Instalados", + "notInstalled": "No instalados" + }, "title": "Marketplace" }, "done": "Hecho", diff --git a/src/i18n/locales/fr/marketplace.json b/src/i18n/locales/fr/marketplace.json index 7a42b0033e..7a6a3dbd74 100644 --- a/src/i18n/locales/fr/marketplace.json +++ b/src/i18n/locales/fr/marketplace.json @@ -38,6 +38,12 @@ "noResults": "Aucune étiquette trouvée.", "selected": "Affichage des éléments avec l'une des étiquettes sélectionnées" }, + "installed": { + "label": "Filtrer par statut", + "all": "Tous les articles", + "installed": "Installés", + "notInstalled": "Non installés" + }, "title": "Marketplace" }, "done": "Terminé", diff --git a/src/i18n/locales/hi/marketplace.json b/src/i18n/locales/hi/marketplace.json index 94013c20e4..3797c50b01 100644 --- a/src/i18n/locales/hi/marketplace.json +++ b/src/i18n/locales/hi/marketplace.json @@ -38,6 +38,12 @@ "noResults": "कोई टैग नहीं मिले।", "selected": "चयनित टैग्स में से किसी भी के साथ आइटम दिखा रहे हैं" }, + "installed": { + "label": "स्थिति के अनुसार फ़िल्टर करें", + "all": "सभी आइटम", + "installed": "स्थापित", + "notInstalled": "स्थापित नहीं" + }, "title": "मार्केटप्लेस" }, "done": "हो गया", diff --git a/src/i18n/locales/id/marketplace.json b/src/i18n/locales/id/marketplace.json index 77d93973a9..7f31c78cbf 100644 --- a/src/i18n/locales/id/marketplace.json +++ b/src/i18n/locales/id/marketplace.json @@ -38,6 +38,12 @@ "noResults": "Tidak ada tag ditemukan.", "selected": "Menampilkan item dengan salah satu tag yang dipilih" }, + "installed": { + "label": "Filter berdasarkan status", + "all": "Semua Item", + "installed": "Terpasang", + "notInstalled": "Tidak Terpasang" + }, "title": "Marketplace" }, "done": "Selesai", diff --git a/src/i18n/locales/it/marketplace.json b/src/i18n/locales/it/marketplace.json index 3cdcd2b76c..46c50acc7d 100644 --- a/src/i18n/locales/it/marketplace.json +++ b/src/i18n/locales/it/marketplace.json @@ -38,6 +38,12 @@ "noResults": "Nessun tag trovato.", "selected": "Mostrando elementi con uno qualsiasi dei tag selezionati" }, + "installed": { + "label": "Filtra per stato", + "all": "Tutti gli articoli", + "installed": "Installati", + "notInstalled": "Non installati" + }, "title": "Marketplace" }, "done": "Fatto", diff --git a/src/i18n/locales/ja/marketplace.json b/src/i18n/locales/ja/marketplace.json index 26cff2ade9..426a5db1fd 100644 --- a/src/i18n/locales/ja/marketplace.json +++ b/src/i18n/locales/ja/marketplace.json @@ -38,6 +38,12 @@ "noResults": "タグが見つかりません。", "selected": "選択されたタグのいずれかを持つアイテムを表示" }, + "installed": { + "label": "ステータスで絞り込む", + "all": "すべてのアイテム", + "installed": "インストール済み", + "notInstalled": "未インストール" + }, "title": "マーケットプレイス" }, "done": "完了", diff --git a/src/i18n/locales/ko/marketplace.json b/src/i18n/locales/ko/marketplace.json index 52bd03edf7..349f0c5ad8 100644 --- a/src/i18n/locales/ko/marketplace.json +++ b/src/i18n/locales/ko/marketplace.json @@ -38,6 +38,12 @@ "noResults": "태그를 찾을 수 없습니다.", "selected": "선택된 태그 중 하나를 가진 항목 표시" }, + "installed": { + "label": "상태별로 필터링", + "all": "모든 항목", + "installed": "설치됨", + "notInstalled": "설치되지 않음" + }, "title": "마켓플레이스" }, "done": "완료", diff --git a/src/i18n/locales/nl/marketplace.json b/src/i18n/locales/nl/marketplace.json index 5628b8f628..1274666b9c 100644 --- a/src/i18n/locales/nl/marketplace.json +++ b/src/i18n/locales/nl/marketplace.json @@ -38,6 +38,12 @@ "noResults": "Geen tags gevonden.", "selected": "Items tonen met een van de geselecteerde tags" }, + "installed": { + "label": "Filteren op status", + "all": "Alle items", + "installed": "Geïnstalleerd", + "notInstalled": "Niet geïnstalleerd" + }, "title": "Marketplace" }, "done": "Klaar", diff --git a/src/i18n/locales/pl/marketplace.json b/src/i18n/locales/pl/marketplace.json index 029dbd95a3..3ba3c076ad 100644 --- a/src/i18n/locales/pl/marketplace.json +++ b/src/i18n/locales/pl/marketplace.json @@ -38,6 +38,12 @@ "noResults": "Nie znaleziono tagów.", "selected": "Pokazywanie elementów z dowolnym z wybranych tagów" }, + "installed": { + "label": "Filtruj według statusu", + "all": "Wszystkie elementy", + "installed": "Zainstalowane", + "notInstalled": "Niezainstalowane" + }, "title": "Marketplace" }, "done": "Gotowe", diff --git a/src/i18n/locales/pt-BR/marketplace.json b/src/i18n/locales/pt-BR/marketplace.json index b0af013888..b0e763b0ff 100644 --- a/src/i18n/locales/pt-BR/marketplace.json +++ b/src/i18n/locales/pt-BR/marketplace.json @@ -38,6 +38,12 @@ "noResults": "Nenhuma tag encontrada.", "selected": "Mostrando itens com qualquer uma das tags selecionadas" }, + "installed": { + "label": "Filtrar por status", + "all": "Todos os itens", + "installed": "Instalados", + "notInstalled": "Não instalados" + }, "title": "Marketplace" }, "done": "Concluído", diff --git a/src/i18n/locales/ru/marketplace.json b/src/i18n/locales/ru/marketplace.json index a84b1ce3e9..1c4ba45df5 100644 --- a/src/i18n/locales/ru/marketplace.json +++ b/src/i18n/locales/ru/marketplace.json @@ -38,6 +38,12 @@ "noResults": "Теги не найдены.", "selected": "Показ элементов с любым из выбранных тегов" }, + "installed": { + "label": "Фильтр по статусу", + "all": "Все элементы", + "installed": "Установленные", + "notInstalled": "Не установленные" + }, "title": "Marketplace" }, "done": "Готово", diff --git a/src/i18n/locales/tr/marketplace.json b/src/i18n/locales/tr/marketplace.json index b08381d71c..628c239a12 100644 --- a/src/i18n/locales/tr/marketplace.json +++ b/src/i18n/locales/tr/marketplace.json @@ -38,6 +38,12 @@ "noResults": "Etiket bulunamadı.", "selected": "Seçilen etiketlerden herhangi birine sahip öğeleri göster" }, + "installed": { + "label": "Duruma göre filtrele", + "all": "Tüm Öğeler", + "installed": "Yüklü", + "notInstalled": "Yüklü Değil" + }, "title": "Marketplace" }, "done": "Tamam", diff --git a/src/i18n/locales/vi/marketplace.json b/src/i18n/locales/vi/marketplace.json index be39054309..8fbc1a7ad0 100644 --- a/src/i18n/locales/vi/marketplace.json +++ b/src/i18n/locales/vi/marketplace.json @@ -38,6 +38,12 @@ "noResults": "Không tìm thấy thẻ nào.", "selected": "Hiển thị các mục có bất kỳ thẻ nào được chọn" }, + "installed": { + "label": "Lọc theo trạng thái", + "all": "Tất cả các mục", + "installed": "Đã cài đặt", + "notInstalled": "Chưa cài đặt" + }, "title": "Marketplace" }, "done": "Hoàn thành", diff --git a/src/i18n/locales/zh-CN/marketplace.json b/src/i18n/locales/zh-CN/marketplace.json index 50a2ade635..a69e9c1e9d 100644 --- a/src/i18n/locales/zh-CN/marketplace.json +++ b/src/i18n/locales/zh-CN/marketplace.json @@ -38,6 +38,12 @@ "noResults": "未找到标签。", "selected": "显示包含任一选中标签的项目" }, + "installed": { + "label": "按状态筛选", + "all": "所有项目", + "installed": "已安装", + "notInstalled": "未安装" + }, "title": "Marketplace" }, "done": "完成", diff --git a/src/i18n/locales/zh-TW/marketplace.json b/src/i18n/locales/zh-TW/marketplace.json index 3eaeb0e7e0..9270314108 100644 --- a/src/i18n/locales/zh-TW/marketplace.json +++ b/src/i18n/locales/zh-TW/marketplace.json @@ -38,6 +38,12 @@ "noResults": "找不到標籤。", "selected": "顯示包含任一選取標籤的項目" }, + "installed": { + "label": "按狀態篩選", + "all": "所有項目", + "installed": "已安裝", + "notInstalled": "未安裝" + }, "title": "Marketplace" }, "done": "完成", diff --git a/webview-ui/src/components/marketplace/MarketplaceListView.tsx b/webview-ui/src/components/marketplace/MarketplaceListView.tsx index 8ec13be8e5..c3c497ccfe 100644 --- a/webview-ui/src/components/marketplace/MarketplaceListView.tsx +++ b/webview-ui/src/components/marketplace/MarketplaceListView.tsx @@ -1,6 +1,7 @@ import * as React from "react" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command" import { X, ChevronsUpDown } from "lucide-react" @@ -27,6 +28,9 @@ export function MarketplaceListView({ stateManager, allTags, filteredTags, filte const allItems = state.displayItems || [] const organizationMcps = state.displayOrganizationMcps || [] + // NOTE: installed metadata is already synchronized into the state manager via handleMessage("state"/"marketplaceData") + // in MarketplaceViewStateManager; avoid dispatching UPDATE_FILTERS here to prevent render loops. + // Filter items by type if specified const items = filterByType ? allItems.filter((item) => item.type === filterByType) : allItems const orgMcps = filterByType === "mcp" ? organizationMcps : [] @@ -55,115 +59,132 @@ export function MarketplaceListView({ stateManager, allTags, filteredTags, filte } /> - {allTags.length > 0 && ( -
-
-
- -
- {state.filters.tags.length > 0 && ( - - )} -
- - setIsTagPopoverOpen(open)}> - - - - e.stopPropagation()}> - -
- - {tagSearch && ( - - )} -
- - - {t("marketplace:filters.tags.noResults")} - - - {filteredTags.map((tag: string) => ( - { - const isSelected = state.filters.tags.includes(tag) - manager.transition({ - type: "UPDATE_FILTERS", - payload: { - filters: { - tags: isSelected - ? state.filters.tags.filter((t) => t !== tag) - : [...state.filters.tags, tag], +
+ + {allTags.length > 0 && ( +
+ setIsTagPopoverOpen(open)}> + + + + e.stopPropagation()}> + +
+ + {tagSearch && ( + + )} +
+ + + {t("marketplace:filters.tags.noResults")} + + + {filteredTags.map((tag: string) => ( + { + const isSelected = state.filters.tags.includes(tag) + manager.transition({ + type: "UPDATE_FILTERS", + payload: { + filters: { + tags: isSelected + ? state.filters.tags.filter( + (t) => t !== tag, + ) + : [...state.filters.tags, tag], + }, }, - }, - }) - }} - data-selected={state.filters.tags.includes(tag)} - className="grid grid-cols-[1rem_1fr] gap-2 cursor-pointer text-sm capitalize" - onMouseDown={(e) => { - e.stopPropagation() - e.preventDefault() - }}> - {state.filters.tags.includes(tag) ? ( - - ) : ( - - )} - {tag} - - ))} - - -
-
-
- {state.filters.tags.length > 0 && ( -
- - {t("marketplace:filters.tags.selected")} -
- )} + }) + }} + data-selected={state.filters.tags.includes(tag)} + className="grid grid-cols-[1rem_1fr] gap-2 cursor-pointer text-sm capitalize" + onMouseDown={(e) => { + e.stopPropagation() + e.preventDefault() + }}> + {state.filters.tags.includes(tag) ? ( + + ) : ( + + )} + {tag} + + ))} + + + + + +
+ )} +
+ {state.filters.tags.length > 0 && ( +
+
+ + {t("marketplace:filters.tags.selected")} +
+
)}
@@ -187,7 +208,7 @@ export function MarketplaceListView({ stateManager, allTags, filteredTags, filte onClick={() => manager.transition({ type: "UPDATE_FILTERS", - payload: { filters: { search: "", type: "", tags: [] } }, + payload: { filters: { search: "", type: "", tags: [], installed: "all" } }, }) } className="mt-4 bg-vscode-button-secondaryBackground text-vscode-button-secondaryForeground hover:bg-vscode-button-secondaryHoverBackground transition-colors"> diff --git a/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts b/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts index 104f3e7cad..995e982164 100644 --- a/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts +++ b/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts @@ -14,6 +14,7 @@ import { MarketplaceItem } from "@roo-code/types" import { vscode } from "../../utils/vscode" import { WebviewMessage } from "../../../../src/shared/WebviewMessage" +import type { MarketplaceInstalledMetadata } from "../../../../src/shared/ExtensionMessage" export interface ViewState { allItems: MarketplaceItem[] @@ -26,7 +27,9 @@ export interface ViewState { type: string search: string tags: string[] + installed: "all" | "installed" | "not_installed" // Filter by installation status } + installedMetadata?: MarketplaceInstalledMetadata // Store installed metadata for filtering } type TransitionPayloads = { @@ -65,6 +68,7 @@ export class MarketplaceViewStateManager { type: "", search: "", tags: [], + installed: "all", }, } } @@ -189,8 +193,11 @@ export class MarketplaceViewStateManager { let newDisplayItems: MarketplaceItem[] let newDisplayOrganizationMcps: MarketplaceItem[] if (this.isFilterActive()) { - newDisplayItems = this.filterItems([...items]) - newDisplayOrganizationMcps = this.filterItems([...this.state.organizationMcps]) + newDisplayItems = this.filterItems([...items], this.state.installedMetadata) + newDisplayOrganizationMcps = this.filterItems( + [...this.state.organizationMcps], + this.state.installedMetadata, + ) } else { // No filters active - show all items newDisplayItems = [...items] @@ -251,6 +258,7 @@ export class MarketplaceViewStateManager { type: filters.type !== undefined ? filters.type : this.state.filters.type, search: filters.search !== undefined ? filters.search : this.state.filters.search, tags: filters.tags !== undefined ? filters.tags : this.state.filters.tags, + installed: filters.installed !== undefined ? filters.installed : this.state.filters.installed, } // Update filters first @@ -260,8 +268,11 @@ export class MarketplaceViewStateManager { } // Apply filters to displayItems and displayOrganizationMcps with the updated filters - const newDisplayItems = this.filterItems(this.state.allItems) - const newDisplayOrganizationMcps = this.filterItems(this.state.organizationMcps) + const newDisplayItems = this.filterItems(this.state.allItems, this.state.installedMetadata) + const newDisplayOrganizationMcps = this.filterItems( + this.state.organizationMcps, + this.state.installedMetadata, + ) // Update state with filtered items this.state = { @@ -284,39 +295,54 @@ export class MarketplaceViewStateManager { } public isFilterActive(): boolean { - return !!(this.state.filters.type || this.state.filters.search || this.state.filters.tags.length > 0) + return !!( + this.state.filters.type || + this.state.filters.search || + this.state.filters.tags.length > 0 || + this.state.filters.installed !== "all" + ) } - public filterItems(items: MarketplaceItem[]): MarketplaceItem[] { - const { type, search, tags } = this.state.filters + public filterItems(items: MarketplaceItem[], installedMetadata?: MarketplaceInstalledMetadata): MarketplaceItem[] { + const { type, search, tags, installed } = this.state.filters + const searchLower = search?.toLowerCase() - return items - .map((item) => { - // Create a copy of the item to modify - const itemCopy = { ...item } + return items.filter((item) => { + // Check type match + if (type && item.type !== type) { + return false + } - // Check specific match conditions for the main item - const typeMatch = !type || item.type === type - const nameMatch = search ? item.name.toLowerCase().includes(search.toLowerCase()) : false - const descriptionMatch = search - ? (item.description || "").toLowerCase().includes(search.toLowerCase()) - : false - const tagMatch = tags.length > 0 ? item.tags?.some((tag) => tags.includes(tag)) : false - - // Determine if the main item matches all filters - const mainItemMatches = - typeMatch && (!search || nameMatch || descriptionMatch) && (!tags.length || tagMatch) - - const hasMatchingSubcomponents = false - - // Return the item if it matches or has matching subcomponents - if (mainItemMatches || Boolean(hasMatchingSubcomponents)) { - return itemCopy + // Check search match + if (searchLower) { + const nameMatch = item.name.toLowerCase().includes(searchLower) + const descriptionMatch = (item.description || "").toLowerCase().includes(searchLower) + if (!nameMatch && !descriptionMatch) { + return false } + } - return null - }) - .filter((item): item is MarketplaceItem => item !== null) + // Check tag match + if (tags.length > 0 && !item.tags?.some((tag) => tags.includes(tag))) { + return false + } + + // Check installed status if filter is active + if (installed !== "all" && installedMetadata) { + const isInstalledGlobally = !!installedMetadata?.global?.[item.id] + const isInstalledInProject = !!installedMetadata?.project?.[item.id] + const isInstalled = isInstalledGlobally || isInstalledInProject + + if (installed === "installed" && !isInstalled) { + return false + } + if (installed === "not_installed" && isInstalled) { + return false + } + } + + return true + }) } public async handleMessage(message: any): Promise { @@ -343,20 +369,29 @@ export class MarketplaceViewStateManager { // Handle state updates for marketplace items // The state.marketplaceItems come from ClineProvider, see the file src/core/webview/ClineProvider.ts const marketplaceItems = message.state.marketplaceItems + const marketplaceInstalledMetadata = message.state.marketplaceInstalledMetadata if (marketplaceItems !== undefined) { // Always use the marketplace items from the extension when they're provided // This ensures fresh data is always displayed const items = [...marketplaceItems] + // Update installed metadata if provided + if (marketplaceInstalledMetadata !== undefined) { + this.state.installedMetadata = marketplaceInstalledMetadata + } + // Calculate display items based on current filters // If no filters are active, show all items // If filters are active, apply filtering let newDisplayItems: MarketplaceItem[] let newDisplayOrganizationMcps: MarketplaceItem[] if (this.isFilterActive()) { - newDisplayItems = this.filterItems(items) - newDisplayOrganizationMcps = this.filterItems(this.state.organizationMcps) + newDisplayItems = this.filterItems(items, this.state.installedMetadata) + newDisplayOrganizationMcps = this.filterItems( + this.state.organizationMcps, + this.state.installedMetadata, + ) } else { // No filters active - show all items newDisplayItems = items @@ -370,6 +405,7 @@ export class MarketplaceViewStateManager { allItems: items, displayItems: newDisplayItems, displayOrganizationMcps: newDisplayOrganizationMcps, + installedMetadata: marketplaceInstalledMetadata || this.state.installedMetadata, } // Notification is handled below after all state parts are processed } @@ -411,14 +447,25 @@ export class MarketplaceViewStateManager { if (message.type === "marketplaceData") { const marketplaceItems = message.marketplaceItems const organizationMcps = message.organizationMcps || [] + const marketplaceInstalledMetadata = message.marketplaceInstalledMetadata if (marketplaceItems !== undefined) { // Always use the marketplace items from the extension when they're provided // This ensures fresh data is always displayed const items = [...marketplaceItems] const orgMcps = [...organizationMcps] - const newDisplayItems = this.isFilterActive() ? this.filterItems(items) : items - const newDisplayOrganizationMcps = this.isFilterActive() ? this.filterItems(orgMcps) : orgMcps + + // Update installed metadata if provided + if (marketplaceInstalledMetadata !== undefined) { + this.state.installedMetadata = marketplaceInstalledMetadata + } + + const newDisplayItems = this.isFilterActive() + ? this.filterItems(items, this.state.installedMetadata) + : items + const newDisplayOrganizationMcps = this.isFilterActive() + ? this.filterItems(orgMcps, this.state.installedMetadata) + : orgMcps // Update state in a single operation this.state = { @@ -428,6 +475,7 @@ export class MarketplaceViewStateManager { organizationMcps: orgMcps, displayItems: newDisplayItems, displayOrganizationMcps: newDisplayOrganizationMcps, + installedMetadata: marketplaceInstalledMetadata || this.state.installedMetadata, } } diff --git a/webview-ui/src/components/marketplace/__tests__/MarketplaceListView.spec.tsx b/webview-ui/src/components/marketplace/__tests__/MarketplaceListView.spec.tsx index d22c381410..02f94001d1 100644 --- a/webview-ui/src/components/marketplace/__tests__/MarketplaceListView.spec.tsx +++ b/webview-ui/src/components/marketplace/__tests__/MarketplaceListView.spec.tsx @@ -27,6 +27,7 @@ const mockState: ViewState = { type: "", search: "", tags: [], + installed: "all", }, } diff --git a/webview-ui/src/components/marketplace/__tests__/MarketplaceViewStateManager.spec.ts b/webview-ui/src/components/marketplace/__tests__/MarketplaceViewStateManager.spec.ts index 089226ccc3..a6ffc59518 100644 --- a/webview-ui/src/components/marketplace/__tests__/MarketplaceViewStateManager.spec.ts +++ b/webview-ui/src/components/marketplace/__tests__/MarketplaceViewStateManager.spec.ts @@ -64,6 +64,7 @@ describe("MarketplaceViewStateManager", () => { type: "", search: "", tags: [], + installed: "all", }) }) diff --git a/webview-ui/src/components/marketplace/components/__tests__/MarketplaceItemCard.spec.tsx b/webview-ui/src/components/marketplace/components/__tests__/MarketplaceItemCard.spec.tsx index 1f1ed9030b..4fdc685192 100644 --- a/webview-ui/src/components/marketplace/components/__tests__/MarketplaceItemCard.spec.tsx +++ b/webview-ui/src/components/marketplace/components/__tests__/MarketplaceItemCard.spec.tsx @@ -78,6 +78,7 @@ describe("MarketplaceItemCard", () => { type: "", search: "", tags: [], + installed: "all" as "all" | "installed" | "not_installed", }, setFilters: vi.fn(), installed: { @@ -158,7 +159,12 @@ describe("MarketplaceItemCard", () => { renderWithProviders(