Skip to content

Commit c067235

Browse files
Remove problematic debounce logic
1 parent 74a6de7 commit c067235

File tree

10 files changed

+644
-431
lines changed

10 files changed

+644
-431
lines changed

src/services/package-manager/PackageManagerManager.ts

Lines changed: 86 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -344,33 +344,47 @@ export class PackageManagerManager {
344344

345345
// Apply filters
346346
const filteredItems = clonedItems.filter((item) => {
347-
// Check if item itself matches type filter
348-
const itemTypeMatch = !filters.type || item.type === filters.type
349-
350-
// Check if any subcomponents match type filter
351-
const subcomponentTypeMatch =
352-
item.items?.some((subItem) => !filters.type || subItem.type === filters.type) ?? false
353-
354-
// Type filter - include if item or any subcomponent matches
355-
if (filters.type && !itemTypeMatch && !subcomponentTypeMatch) {
356-
return false
347+
// Check parent item matches
348+
const itemMatches = {
349+
type: !filters.type || item.type === filters.type,
350+
search: !searchTerm || containsSearchTerm(item.name) || containsSearchTerm(item.description),
351+
tags: !filters.tags?.length || (item.tags && filters.tags.some((tag) => item.tags!.includes(tag))),
357352
}
358353

359-
// Search filter
360-
if (searchTerm) {
361-
const nameMatch = containsSearchTerm(item.name)
362-
const descMatch = containsSearchTerm(item.description)
363-
const subcomponentMatch =
364-
item.items?.some(
365-
(subItem) =>
366-
subItem.metadata &&
367-
(containsSearchTerm(subItem.metadata.name) ||
368-
containsSearchTerm(subItem.metadata.description)),
369-
) ?? false
370-
return nameMatch || descMatch || subcomponentMatch
371-
}
354+
// Check subcomponent matches
355+
const subcomponentMatches =
356+
item.items?.some((subItem) => {
357+
const subMatches = {
358+
type: !filters.type || subItem.type === filters.type,
359+
search:
360+
!searchTerm ||
361+
(subItem.metadata &&
362+
(containsSearchTerm(subItem.metadata.name) ||
363+
containsSearchTerm(subItem.metadata.description))),
364+
tags:
365+
!filters.tags?.length ||
366+
(subItem.metadata?.tags &&
367+
filters.tags.some((tag) => subItem.metadata!.tags!.includes(tag))),
368+
}
372369

373-
return true
370+
// When filtering by type, require exact type match
371+
// For other filters (search/tags), any match is sufficient
372+
return (
373+
subMatches.type &&
374+
(!searchTerm || subMatches.search) &&
375+
(!filters.tags?.length || subMatches.tags)
376+
)
377+
}) ?? false
378+
379+
// Include item if either:
380+
// 1. Parent matches all active filters, or
381+
// 2. Parent is a package and any subcomponent matches any active filter
382+
const hasActiveFilters = filters.type || searchTerm || filters.tags?.length
383+
if (!hasActiveFilters) return true
384+
385+
const parentMatchesAll = itemMatches.type && itemMatches.search && itemMatches.tags
386+
const isPackageWithMatchingSubcomponent = item.type === "package" && subcomponentMatches
387+
return parentMatchesAll || isPackageWithMatchingSubcomponent
374388
})
375389

376390
console.log("Filtered items:", {
@@ -380,40 +394,45 @@ export class PackageManagerManager {
380394
})
381395
// Add match info to filtered items
382396
return filteredItems.map((item) => {
383-
const nameMatch = searchTerm ? containsSearchTerm(item.name) : true
384-
const descMatch = searchTerm ? containsSearchTerm(item.description) : true
385-
const typeMatch = filters.type ? item.type === filters.type : true
397+
// Calculate parent item matches
398+
const itemMatches = {
399+
type: !filters.type || item.type === filters.type,
400+
search: !searchTerm || containsSearchTerm(item.name) || containsSearchTerm(item.description),
401+
tags: !filters.tags?.length || (item.tags && filters.tags.some((tag) => item.tags!.includes(tag))),
402+
}
386403

387-
// Process subcomponents first to determine if any match
404+
// Process subcomponents
388405
let hasMatchingSubcomponents = false
389406
if (item.items) {
390407
item.items = item.items.map((subItem) => {
391-
// Calculate matches
392-
const subNameMatch =
393-
searchTerm && subItem.metadata ? containsSearchTerm(subItem.metadata.name) : true
394-
const subDescMatch =
395-
searchTerm && subItem.metadata ? containsSearchTerm(subItem.metadata.description) : true
396-
397-
// Only calculate type match if type filter is active
398-
const subTypeMatch = filters.type ? subItem.type === filters.type : false
408+
// Calculate individual filter matches for subcomponent
409+
const subMatches = {
410+
type: !filters.type || subItem.type === filters.type,
411+
search:
412+
!searchTerm ||
413+
(subItem.metadata &&
414+
(containsSearchTerm(subItem.metadata.name) ||
415+
containsSearchTerm(subItem.metadata.description))),
416+
tags:
417+
!filters.tags?.length ||
418+
(subItem.metadata?.tags &&
419+
filters.tags.some((tag) => subItem.metadata!.tags!.includes(tag))),
420+
}
399421

400-
// Determine if item matches based on active filters
401-
const subMatched = filters.type
402-
? subNameMatch || subDescMatch || subTypeMatch
403-
: subNameMatch || subDescMatch
422+
// A subcomponent matches if it matches all active filters
423+
const subMatched = subMatches.type && subMatches.search && subMatches.tags
404424

405425
if (subMatched) {
406426
hasMatchingSubcomponents = true
407-
408-
// Only include matchReason if the item matches
427+
// Build match reason for matched subcomponent
409428
const matchReason: Record<string, boolean> = {
410-
nameMatch: subNameMatch,
411-
descriptionMatch: subDescMatch,
412-
}
413-
414-
// Only include type match in reason if type filter is active
415-
if (filters.type) {
416-
matchReason.typeMatch = subTypeMatch
429+
...(searchTerm && {
430+
nameMatch: !!subItem.metadata && containsSearchTerm(subItem.metadata.name),
431+
descriptionMatch:
432+
!!subItem.metadata && containsSearchTerm(subItem.metadata.description),
433+
}),
434+
...(filters.type && { typeMatch: subMatches.type }),
435+
...(filters.tags?.length && { tagMatch: !!subMatches.tags }),
417436
}
418437

419438
subItem.matchInfo = {
@@ -430,21 +449,34 @@ export class PackageManagerManager {
430449
})
431450
}
432451

452+
// Build match reason for parent item
433453
const matchReason: Record<string, boolean> = {
434-
nameMatch,
435-
descriptionMatch: descMatch,
454+
nameMatch: searchTerm ? containsSearchTerm(item.name) : true,
455+
descriptionMatch: searchTerm ? containsSearchTerm(item.description) : true,
436456
}
437457

438-
// Only include typeMatch and hasMatchingSubcomponents in matchReason if relevant
439458
if (filters.type) {
440-
matchReason.typeMatch = typeMatch
459+
matchReason.typeMatch = itemMatches.type
460+
}
461+
if (filters.tags?.length) {
462+
matchReason.tagMatch = !!itemMatches.tags
441463
}
442464
if (hasMatchingSubcomponents) {
443465
matchReason.hasMatchingSubcomponents = true
444466
}
445467

468+
// Parent item is matched if:
469+
// 1. It matches all active filters directly, or
470+
// 2. It's a package and has any matching subcomponents
471+
const parentMatchesAll =
472+
(!filters.type || itemMatches.type) &&
473+
(!searchTerm || itemMatches.search) &&
474+
(!filters.tags?.length || itemMatches.tags)
475+
476+
const isPackageWithMatchingSubcomponent = item.type === "package" && hasMatchingSubcomponents
477+
446478
item.matchInfo = {
447-
matched: nameMatch || descMatch || typeMatch || hasMatchingSubcomponents,
479+
matched: parentMatchesAll || isPackageWithMatchingSubcomponent,
448480
matchReason,
449481
}
450482

src/services/package-manager/__tests__/PackageManagerManager.test.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,80 @@ describe("PackageManagerManager", () => {
131131
describe("Type Filter Behavior", () => {
132132
let typeFilterTestItems: PackageManagerItem[]
133133

134+
test("should include package with MCP server subcomponent when filtering by type 'mcp server'", () => {
135+
const items: PackageManagerItem[] = [
136+
{
137+
name: "Data Platform Package",
138+
description: "A package containing MCP servers",
139+
type: "package" as ComponentType,
140+
url: "test/package",
141+
repoUrl: "https://example.com",
142+
items: [
143+
{
144+
type: "mcp server" as ComponentType,
145+
path: "test/server",
146+
metadata: {
147+
name: "Data Validator",
148+
description: "An MCP server",
149+
version: "1.0.0",
150+
type: "mcp server" as ComponentType,
151+
},
152+
},
153+
],
154+
},
155+
{
156+
name: "Standalone Server",
157+
description: "A standalone MCP server",
158+
type: "mcp server" as ComponentType,
159+
url: "test/server",
160+
repoUrl: "https://example.com",
161+
},
162+
]
163+
164+
const filtered = manager.filterItems(items, { type: "mcp server" })
165+
expect(filtered.length).toBe(2)
166+
expect(filtered.map((item) => item.name)).toContain("Data Platform Package")
167+
expect(filtered.map((item) => item.name)).toContain("Standalone Server")
168+
169+
// Verify package is included because of its MCP server subcomponent
170+
const pkg = filtered.find((item) => item.name === "Data Platform Package")
171+
expect(pkg?.matchInfo?.matched).toBe(true)
172+
expect(pkg?.matchInfo?.matchReason?.hasMatchingSubcomponents).toBe(true)
173+
expect(pkg?.items?.[0].matchInfo?.matched).toBe(true)
174+
expect(pkg?.items?.[0].matchInfo?.matchReason?.typeMatch).toBe(true)
175+
})
176+
177+
test("should include package when filtering by subcomponent type", () => {
178+
const items: PackageManagerItem[] = [
179+
{
180+
name: "Data Platform Package",
181+
description: "A package containing MCP servers",
182+
type: "package" as ComponentType,
183+
url: "test/package",
184+
repoUrl: "https://example.com",
185+
items: [
186+
{
187+
type: "mcp server" as ComponentType,
188+
path: "test/server",
189+
metadata: {
190+
name: "Data Validator",
191+
description: "An MCP server",
192+
version: "1.0.0",
193+
type: "mcp server" as ComponentType,
194+
},
195+
},
196+
],
197+
},
198+
]
199+
200+
const filtered = manager.filterItems(items, { type: "mcp server" })
201+
expect(filtered.length).toBe(1)
202+
expect(filtered[0].name).toBe("Data Platform Package")
203+
expect(filtered[0].matchInfo?.matched).toBe(true)
204+
expect(filtered[0].items?.[0].matchInfo?.matched).toBe(true)
205+
expect(filtered[0].items?.[0].matchInfo?.matchReason?.typeMatch).toBe(true)
206+
})
207+
134208
beforeEach(() => {
135209
// Create test items
136210
typeFilterTestItems = [

webview-ui/src/components/package-manager/PackageManagerView.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { Tab, TabContent, TabHeader } from "../common/Tab"
44
import { cn } from "@/lib/utils"
55
import { PackageManagerSource } from "../../../../src/services/package-manager/types"
66
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "cmdk"
7-
import { isFilterActive as checkFilterActive } from "./selectors"
87
import { PackageManagerItemCard } from "./components/PackageManagerItemCard"
98
import { useStateManager } from "./useStateManager"
109

@@ -85,8 +84,11 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
8584
<div className="flex flex-col gap-3 mt-2">
8685
<div className="flex flex-wrap justify-between gap-2">
8786
<div className="whitespace-nowrap">
88-
<label className="mr-2">Filter by type:</label>
87+
<label htmlFor="type-filter" className="mr-2">
88+
Filter by type:
89+
</label>
8990
<select
91+
id="type-filter"
9092
value={state.filters.type}
9193
onChange={(e) =>
9294
manager.transition({
@@ -239,10 +241,8 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
239241
</div>
240242

241243
{(() => {
242-
// Debug log state
243-
const items = checkFilterActive(state.filters)
244-
? state.displayItems || []
245-
: state.allItems || []
244+
// Use items directly from backend
245+
const items = state.displayItems || []
246246
const isEmpty = items.length === 0
247247
const isLoading = state.isFetching
248248
console.log("=== Rendering PackageManagerView ===")
@@ -256,7 +256,11 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
256256
})
257257

258258
// Show loading state if fetching and not filtering
259-
if (isLoading && !checkFilterActive(state.filters)) {
259+
// Only show loading state if we're fetching and not filtering
260+
if (
261+
isLoading &&
262+
!(state.filters.type || state.filters.search || state.filters.tags.length > 0)
263+
) {
260264
console.log("Rendering loading state due to isFetching=true")
261265
return (
262266
<div className="flex flex-col items-center justify-center h-64 text-vscode-descriptionForeground">
@@ -280,7 +284,7 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
280284
return (
281285
<div>
282286
<p className="text-vscode-descriptionForeground mb-4">
283-
{checkFilterActive(state.filters)
287+
{state.filters.type || state.filters.search || state.filters.tags.length > 0
284288
? `${items.length} items found (filtered)`
285289
: `${items.length} ${items.length === 1 ? "item" : "items"} total`}
286290
</p>

0 commit comments

Comments
 (0)