From 45eadab047e820bc020149e0f7f92e7feb09a8d6 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Tue, 23 Jun 2026 13:42:24 +0200 Subject: [PATCH 1/9] feat(vscode): unify marketplace browsing --- .changeset/mix-marketplace-categories.md | 5 ++ .../src/services/marketplace/detection.ts | 12 ++- .../src/services/marketplace/types.ts | 3 +- .../tests/unit/marketplace-actions.test.ts | 57 ++++++++++++ .../tests/unit/marketplace-installer.test.ts | 5 ++ .../src/components/marketplace/ItemCard.tsx | 6 ++ .../marketplace/MarketplaceListView.tsx | 85 +++++++----------- .../marketplace/MarketplaceView.tsx | 89 ++++--------------- .../components/marketplace/marketplace.css | 40 ++++----- .../src/components/marketplace/utils.ts | 35 +++++++- .../src/stories/marketplace.stories.tsx | 88 ++++++++++-------- .../webview-ui/src/types/marketplace.ts | 5 +- 12 files changed, 237 insertions(+), 193 deletions(-) create mode 100644 .changeset/mix-marketplace-categories.md diff --git a/.changeset/mix-marketplace-categories.md b/.changeset/mix-marketplace-categories.md new file mode 100644 index 00000000000..f922d06b6b7 --- /dev/null +++ b/.changeset/mix-marketplace-categories.md @@ -0,0 +1,5 @@ +--- +"kilo-code": minor +--- + +Browse skills, agents, and MCP servers together and filter them by category. diff --git a/packages/kilo-vscode/src/services/marketplace/detection.ts b/packages/kilo-vscode/src/services/marketplace/detection.ts index fb2030beb7d..d7a3a20eb84 100644 --- a/packages/kilo-vscode/src/services/marketplace/detection.ts +++ b/packages/kilo-vscode/src/services/marketplace/detection.ts @@ -5,6 +5,10 @@ import { MarketplacePaths } from "./paths" type Entry = [string, { type: string }] +function entry(id: string, type: "agent" | "mcp" | "skill"): Entry { + return [`${type}:${id}`, { type }] +} + export interface CliSkill { name: string location: string @@ -52,7 +56,7 @@ export class InstallationDetector { ? !!workspace && this.isProjectSkill(s.location, workspace) : !workspace || !this.isProjectSkill(s.location, workspace), ) - .map((s) => [s.name, { type: "skill" }]) + .map((skill) => entry(skill.name, "skill")) } /** Scan .kilo/agents/*.md files to detect installed marketplace agents. */ @@ -60,7 +64,7 @@ export class InstallationDetector { const dir = this.paths.agentsDir(scope, workspace) try { const files = await fs.readdir(dir) - return files.filter((f) => f.endsWith(".md")).map((f) => [path.basename(f, ".md"), { type: "agent" }] as Entry) + return files.filter((file) => file.endsWith(".md")).map((file) => entry(path.basename(file, ".md"), "agent")) } catch (err) { if ((err as NodeJS.ErrnoException).code !== "ENOENT") { console.warn(`Failed to detect agent files from ${dir}:`, err) @@ -78,13 +82,13 @@ export class InstallationDetector { if (parsed?.mcp && typeof parsed.mcp === "object") { for (const key of Object.keys(parsed.mcp)) { - entries.push([key, { type: "mcp" }]) + entries.push(entry(key, "mcp")) } } if (parsed?.agent && typeof parsed.agent === "object") { for (const key of Object.keys(parsed.agent)) { - entries.push([key, { type: "agent" }]) + entries.push(entry(key, "agent")) } } diff --git a/packages/kilo-vscode/src/services/marketplace/types.ts b/packages/kilo-vscode/src/services/marketplace/types.ts index 81f1c6f031f..1f544860b01 100644 --- a/packages/kilo-vscode/src/services/marketplace/types.ts +++ b/packages/kilo-vscode/src/services/marketplace/types.ts @@ -16,9 +16,9 @@ export interface MarketplaceItemBase { id: string name: string description: string + category: string author?: string authorUrl?: string - tags?: string[] prerequisites?: string[] } @@ -52,7 +52,6 @@ export interface RawSkill { export interface SkillMarketplaceItem extends MarketplaceItemBase { type: "skill" - category: string githubUrl: string content: string displayName: string diff --git a/packages/kilo-vscode/tests/unit/marketplace-actions.test.ts b/packages/kilo-vscode/tests/unit/marketplace-actions.test.ts index d0f5962110b..aacc681a9ff 100644 --- a/packages/kilo-vscode/tests/unit/marketplace-actions.test.ts +++ b/packages/kilo-vscode/tests/unit/marketplace-actions.test.ts @@ -7,6 +7,8 @@ import { type MarketplaceRemoveContext, } from "../../src/services/marketplace/actions" import type { McpMarketplaceItem } from "../../src/services/marketplace/types" +import { filterItems, installedScopes } from "../../webview-ui/src/components/marketplace/utils" +import type { MarketplaceItem } from "../../webview-ui/src/types/marketplace" const project = "/repo" const storage = vscode.Uri.file("/storage") @@ -18,6 +20,7 @@ const item: McpMarketplaceItem = { type: "mcp", name: "Memory", description: "", + category: "development", url: "", content: "", } @@ -62,6 +65,60 @@ afterEach(() => { fs.writeFile = original.writeFile }) +describe("Marketplace installation metadata", () => { + it("tracks colliding IDs independently by item type", () => { + const metadata = { + project: { + "mcp:dbt": { type: "mcp" }, + "skill:dbt": { type: "skill" }, + }, + global: {}, + } + + expect(installedScopes("dbt", "mcp", metadata)).toEqual(["project"]) + expect(installedScopes("dbt", "skill", metadata)).toEqual(["project"]) + expect(installedScopes("dbt", "agent", metadata)).toEqual([]) + }) + + it("filters the mixed list by search, category, and status", () => { + const items: MarketplaceItem[] = [ + { + type: "agent", + id: "reviewer", + name: "Code Reviewer", + description: "Reviews code", + category: "development", + content: { mode: "all", description: "Reviews code", prompt: "Review" }, + }, + { + type: "mcp", + id: "warehouse", + name: "Warehouse", + description: "Queries data", + category: "data", + url: "https://example.com", + content: "{}", + }, + { + type: "skill", + id: "campaign-writer", + name: "Campaign Writer", + displayName: "Campaign Writer", + description: "Writes campaigns", + category: "business", + displayCategory: "Business", + githubUrl: "https://example.com", + content: "https://example.com/skill.tar.gz", + }, + ] + const metadata = { project: { "mcp:warehouse": { type: "mcp" } }, global: {} } + + expect(filterItems(items, metadata, "reviewer", "all", []).map((item) => item.id)).toEqual(["reviewer"]) + expect(filterItems(items, metadata, "", "all", ["business"]).map((item) => item.id)).toEqual(["campaign-writer"]) + expect(filterItems(items, metadata, "", "installed", []).map((item) => item.id)).toEqual(["warehouse"]) + }) +}) + describe("Marketplace legacy MCP cleanup", () => { it("preserves global legacy config during project removal", async () => { const files = setup() diff --git a/packages/kilo-vscode/tests/unit/marketplace-installer.test.ts b/packages/kilo-vscode/tests/unit/marketplace-installer.test.ts index 1991576cece..a576f64d95c 100644 --- a/packages/kilo-vscode/tests/unit/marketplace-installer.test.ts +++ b/packages/kilo-vscode/tests/unit/marketplace-installer.test.ts @@ -55,6 +55,7 @@ describe("MarketplaceInstaller MCP format normalization", () => { id: "memory", name: "Memory", description: "test", + category: "development", url: "https://example.com", content: JSON.stringify({ command: "npx", @@ -81,6 +82,7 @@ describe("MarketplaceInstaller MCP format normalization", () => { id: "myremote", name: "Remote", description: "test", + category: "development", url: "https://example.com", content: JSON.stringify({ type: "sse", @@ -105,6 +107,7 @@ describe("MarketplaceInstaller MCP format normalization", () => { id: "already", name: "Already Done", description: "test", + category: "development", url: "https://example.com", content: JSON.stringify({ type: "local", @@ -153,6 +156,7 @@ describe("MarketplaceInstaller skills", () => { id: "test-mcp", name: "Test MCP", description: "test", + category: "development", url: "https://example.com", content: "{}", }, @@ -164,6 +168,7 @@ describe("MarketplaceInstaller skills", () => { id: "test-agent", name: "Test Agent", description: "test", + category: "development", content: { mode: "all", description: "test", prompt: "test" }, }, "project", diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/ItemCard.tsx b/packages/kilo-vscode/webview-ui/src/components/marketplace/ItemCard.tsx index b74e556edeb..c82127b96d1 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/ItemCard.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/ItemCard.tsx @@ -23,6 +23,11 @@ export const ItemCard = (props: Props) => { const scopes = () => installedScopes(props.item.id, props.item.type, props.metadata) const installed = () => scopes().length > 0 const name = () => props.displayName ?? props.item.name + const type = () => { + if (props.item.type === "mcp") return t("marketplace.badge.mcpServer") + if (props.item.type === "agent") return t("marketplace.remove.type.agent") + return t("marketplace.remove.type.skill") + } const [expanded, setExpanded] = createSignal(false) const [clamped, setClamped] = createSignal(false) let ref: HTMLParagraphElement | undefined @@ -44,6 +49,7 @@ export const ItemCard = (props: Props) => { {name()} + {type()} diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceListView.tsx b/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceListView.tsx index 8bb615fa386..4bbba1f7e03 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceListView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceListView.tsx @@ -1,4 +1,4 @@ -import { createSignal, createMemo, For, Show } from "solid-js" +import { createSignal, createMemo, createEffect, For, Show } from "solid-js" import { TextField } from "@kilocode/kilo-ui/text-field" import { Select } from "@kilocode/kilo-ui/select" import { Tag } from "@kilocode/kilo-ui/tag" @@ -10,7 +10,7 @@ import type { MarketplaceInstalledMetadata, } from "../../types/marketplace" import { useLanguage } from "../../context/language" -import { isInstalled } from "./utils" +import { filterItems } from "./utils" import { ItemCard } from "./ItemCard" import { MarketplaceContribute } from "./MarketplaceContribute" @@ -23,7 +23,6 @@ interface Props { items: MarketplaceItem[] metadata: MarketplaceInstalledMetadata fetching: boolean - type: "mcp" | "agent" | "skill" searchPlaceholder: string emptyMessage: string onInstall: (item: MarketplaceItem) => void @@ -34,7 +33,7 @@ export const MarketplaceListView = (props: Props) => { const { t } = useLanguage() const [search, setSearch] = createSignal("") const [status, setStatus] = createSignal({ value: "all", label: t("marketplace.filter.all") }) - const [tags, setTags] = createSignal([]) + const [categories, setCategories] = createSignal([]) const options = (): StatusOption[] => [ { value: "all", label: t("marketplace.filter.all") }, @@ -42,51 +41,32 @@ export const MarketplaceListView = (props: Props) => { { value: "notInstalled", label: t("marketplace.filter.notInstalled") }, ] - const tagsFor = (item: MarketplaceItem): string[] => { - if (item.type === "skill") return [(item as SkillMarketplaceItem).displayCategory] - return item.tags ?? [] - } + const label = (category: string) => + category + .split("-") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" ") - const allTags = createMemo(() => { - const counts = new Map() - for (const item of props.items) { - for (const tag of tagsFor(item)) counts.set(tag, (counts.get(tag) ?? 0) + 1) - } - const min = props.type === "mcp" ? 5 : 1 - return Array.from(counts.entries()) - .filter(([, n]) => n >= min) - .map(([tag]) => tag) - .sort() + const allCategories = createMemo(() => Array.from(new Set(props.items.map((item) => item.category))).sort()) + + createEffect(() => { + const available = new Set(allCategories()) + setCategories((current) => { + const next = current.filter((category) => available.has(category)) + return next.length === current.length ? current : next + }) }) - const toggleTag = (tag: string) => { - const current = tags() - if (current.includes(tag)) { - setTags(current.filter((t) => t !== tag)) - } else { - setTags([...current, tag]) + const toggleCategory = (category: string) => { + const current = categories() + if (current.includes(category)) { + setCategories(current.filter((value) => value !== category)) + return } + setCategories([...current, category]) } - const filtered = createMemo(() => { - const q = search().toLowerCase() - const s = status().value - const active = tags() - return props.items.filter((item) => { - if (s === "installed" && !isInstalled(item.id, item.type, props.metadata)) return false - if (s === "notInstalled" && isInstalled(item.id, item.type, props.metadata)) return false - if (active.length > 0 && !active.some((tag) => tagsFor(item).includes(tag))) return false - if (!q) return true - const skill = item.type === "skill" ? (item as SkillMarketplaceItem) : undefined - return ( - item.id.toLowerCase().includes(q) || - item.name.toLowerCase().includes(q) || - item.description.toLowerCase().includes(q) || - (item.author?.toLowerCase().includes(q) ?? false) || - (skill?.displayName.toLowerCase().includes(q) ?? false) - ) - }) - }) + const filtered = createMemo(() => filterItems(props.items, props.metadata, search(), status().value, categories())) return (
@@ -102,16 +82,17 @@ export const MarketplaceListView = (props: Props) => { onSelect={(v: StatusOption | undefined) => v && setStatus(v)} />
- 0}> -
- - {(tag) => ( + 0}> +
+ + {(category) => ( )} @@ -147,7 +128,7 @@ export const MarketplaceListView = (props: Props) => { linkUrl={skill?.githubUrl ?? mcp?.url} onInstall={props.onInstall} onRemove={props.onRemove} - footer={{(tag) => {tag}}} + footer={{label(item.category)}} /> ) }} diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceView.tsx b/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceView.tsx index b8deefc54e3..8892ec1aa11 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceView.tsx @@ -1,18 +1,11 @@ -import { createSignal, createMemo, createEffect, onCleanup, onMount, Show } from "solid-js" -import { Tabs } from "@kilocode/kilo-ui/tabs" +import { createSignal, createEffect, onCleanup, onMount, Show } from "solid-js" import { Card } from "@kilocode/kilo-ui/card" import { Button } from "@kilocode/kilo-ui/button" import { useVSCode } from "../../context/vscode" import { useServer } from "../../context/server" import { useLanguage } from "../../context/language" import { useDialog } from "@kilocode/kilo-ui/context/dialog" -import type { - MarketplaceItem, - McpMarketplaceItem, - AgentMarketplaceItem, - SkillMarketplaceItem, - MarketplaceInstalledMetadata, -} from "../../types/marketplace" +import type { MarketplaceItem, MarketplaceInstalledMetadata } from "../../types/marketplace" import { TelemetryEventName } from "../../../../src/services/telemetry/types" import { MarketplaceListView } from "./MarketplaceListView" import { InstallModal } from "./InstallModal" @@ -31,14 +24,9 @@ export const MarketplaceView = () => { const [metadata, setMetadata] = createSignal(EMPTY_METADATA) const [fetching, setFetching] = createSignal(true) const [errors, setErrors] = createSignal([]) - const [tab, setTab] = createSignal("agent") const [pending, setPending] = createSignal<{ item: MarketplaceItem; scope: "project" | "global" } | null>(null) const [showMigrationBanner, setShowMigrationBanner] = createSignal(false) - const skills = createMemo(() => items().filter((i): i is SkillMarketplaceItem => i.type === "skill")) - const mcps = createMemo(() => items().filter((i): i is McpMarketplaceItem => i.type === "mcp")) - const agents = createMemo(() => items().filter((i): i is AgentMarketplaceItem => i.type === "agent")) - const fetchData = () => { setFetching(true) vscode.postMessage({ type: "fetchMarketplaceData" }) @@ -158,62 +146,23 @@ export const MarketplaceView = () => { ))} - - - {t("marketplace.tab.agents")} - {t("marketplace.tab.mcp")} - {t("marketplace.tab.skills")} - - -
- - - - {t("marketplace.migration.notice")} - - - - - - - - - - - - - -
-
+ + + {t("marketplace.migration.notice")} + + + +
) } diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css b/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css index b783ef5b59a..45beaca8006 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css @@ -9,25 +9,12 @@ gap: 8px; } -.marketplace-tabs-root { - display: flex; - flex-direction: column; - flex: 1; - min-height: 0; -} - -.marketplace-content { - flex: 1; - overflow: auto; -} - /* List layout */ .marketplace-list { display: flex; flex-direction: column; gap: 12px; - padding-top: 12px; } .marketplace-filters { @@ -40,30 +27,31 @@ flex: 1; } -.marketplace-category-filters { - overflow-x: auto; -} - -/* Tag filters */ +/* Category filters */ -.marketplace-active-tags { +.marketplace-categories { display: flex; flex-wrap: wrap; gap: 4px; } -.marketplace-tag-filter { +.marketplace-category-filter { all: unset; cursor: pointer; opacity: 0.5; transition: opacity 0.15s ease; } -.marketplace-tag-filter:hover { +.marketplace-category-filter:hover { opacity: 0.8; } -.marketplace-tag-filter.active { +.marketplace-category-filter:focus-visible { + outline: 1px solid var(--vscode-focusBorder); + outline-offset: 2px; +} + +.marketplace-category-filter.active { opacity: 1; } @@ -210,6 +198,10 @@ /* Installed badge */ +.marketplace-badge-type { + flex-shrink: 0; +} + .marketplace-badge-installed { color: var(--text-on-success-base) !important; border-color: var(--border-success-base) !important; @@ -326,11 +318,11 @@ body.vscode-high-contrast-light { border: 1px solid var(--vscode-contrastBorder, transparent); } - .marketplace-tag-filter { + .marketplace-category-filter { border: 1px solid var(--vscode-contrastBorder, transparent); } - .marketplace-tag-filter.active { + .marketplace-category-filter.active { border-color: var(--vscode-contrastActiveBorder, var(--vscode-focusBorder)); } diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts b/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts index 9504e734f37..66a7910a2aa 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts @@ -1,4 +1,4 @@ -import type { MarketplaceInstalledMetadata } from "../../types/marketplace" +import type { MarketplaceInstalledMetadata, MarketplaceItem } from "../../types/marketplace" export function isInstalled( id: string, @@ -14,7 +14,36 @@ export function installedScopes( metadata: MarketplaceInstalledMetadata, ): ("project" | "global")[] { const scopes: ("project" | "global")[] = [] - if (metadata.project[id]?.type === type) scopes.push("project") - if (metadata.global[id]?.type === type) scopes.push("global") + const key = `${type}:${id}` + if (metadata.project[key]?.type === type) scopes.push("project") + if (metadata.global[key]?.type === type) scopes.push("global") return scopes } + +export function filterItems( + items: MarketplaceItem[], + metadata: MarketplaceInstalledMetadata, + search: string, + status: string, + categories: string[], +): MarketplaceItem[] { + const query = search.trim().toLowerCase() + return items + .filter((item) => { + if (status === "installed" && !isInstalled(item.id, item.type, metadata)) return false + if (status === "notInstalled" && isInstalled(item.id, item.type, metadata)) return false + if (categories.length > 0 && !categories.includes(item.category)) return false + if (!query) return true + const skill = item.type === "skill" ? item : undefined + return ( + item.id.toLowerCase().includes(query) || + item.name.toLowerCase().includes(query) || + item.description.toLowerCase().includes(query) || + item.category.toLowerCase().includes(query) || + item.type.includes(query) || + (item.author?.toLowerCase().includes(query) ?? false) || + (skill?.displayName.toLowerCase().includes(query) ?? false) + ) + }) + .sort((a, b) => a.name.localeCompare(b.name)) +} diff --git a/packages/kilo-vscode/webview-ui/src/stories/marketplace.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/marketplace.stories.tsx index 62b7f321f1b..498bac128b0 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/marketplace.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/marketplace.stories.tsx @@ -115,7 +115,7 @@ const MOCK_MCPS: McpMarketplaceItem[] = [ '{ "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"], "env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" } }', parameters: [{ name: "GitHub Token", key: "GITHUB_TOKEN", placeholder: "ghp_xxxxxxxxxxxx" }], author: "Anthropic", - tags: ["version-control", "development"], + category: "development", }, { type: "mcp", @@ -143,7 +143,7 @@ const MOCK_MCPS: McpMarketplaceItem[] = [ }, ], author: "Anthropic", - tags: ["database", "sql"], + category: "data", }, { type: "mcp", @@ -153,7 +153,7 @@ const MOCK_MCPS: McpMarketplaceItem[] = [ url: "https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem", content: '{ "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "${ALLOWED_DIR}"] }', parameters: [{ name: "Allowed Directory", key: "ALLOWED_DIR", placeholder: "/path/to/directory" }], - tags: ["filesystem", "development"], + category: "development", }, { type: "mcp", @@ -165,7 +165,7 @@ const MOCK_MCPS: McpMarketplaceItem[] = [ '{ "command": "npx", "args": ["-y", "@modelcontextprotocol/server-slack"], "env": { "SLACK_TOKEN": "${SLACK_TOKEN}" } }', parameters: [{ name: "Slack Bot Token", key: "SLACK_TOKEN", placeholder: "xoxb-xxxxxxxxxxxx" }], author: "Anthropic", - tags: ["communication", "productivity"], + category: "productivity", }, { type: "mcp", @@ -176,7 +176,7 @@ const MOCK_MCPS: McpMarketplaceItem[] = [ content: '{ "command": "npx", "args": ["-y", "@modelcontextprotocol/server-brave-search"], "env": { "BRAVE_API_KEY": "${BRAVE_API_KEY}" } }', parameters: [{ name: "API Key", key: "BRAVE_API_KEY", placeholder: "BSA-xxxxxxxxxxxx" }], - tags: ["search", "web"], + category: "search", }, { type: "mcp", @@ -186,7 +186,7 @@ const MOCK_MCPS: McpMarketplaceItem[] = [ url: "https://github.com/modelcontextprotocol/servers/tree/main/src/puppeteer", content: '{ "command": "npx", "args": ["-y", "@modelcontextprotocol/server-puppeteer"] }', prerequisites: ["Chrome or Chromium must be installed"], - tags: ["browser", "automation", "web"], + category: "web-automation", }, ] @@ -205,7 +205,7 @@ const MOCK_AGENTS: AgentMarketplaceItem[] = [ permission: { read: "allow", edit: "deny", bash: "deny", mcp: "deny", question: "allow" }, }, author: "Kilo", - tags: ["planning", "design"], + category: "development", }, { type: "agent", @@ -221,7 +221,7 @@ const MOCK_AGENTS: AgentMarketplaceItem[] = [ permission: { read: "allow", edit: "deny", bash: "allow", mcp: "deny", question: "allow" }, }, author: "Kilo", - tags: ["review", "quality"], + category: "development", }, { type: "agent", @@ -235,7 +235,7 @@ const MOCK_AGENTS: AgentMarketplaceItem[] = [ options: { displayName: "Documentation Writer" }, permission: { read: "allow", edit: "allow", bash: "allow", mcp: "deny", question: "allow" }, }, - tags: ["documentation", "writing"], + category: "business", }, { type: "agent", @@ -251,7 +251,7 @@ const MOCK_AGENTS: AgentMarketplaceItem[] = [ permission: { read: "allow", edit: "allow", bash: "allow", mcp: "allow", question: "allow" }, }, author: "Community", - tags: ["testing", "methodology"], + category: "development", }, { type: "agent", @@ -265,25 +265,33 @@ const MOCK_AGENTS: AgentMarketplaceItem[] = [ options: { displayName: "Debugger" }, permission: { read: "allow", edit: "allow", bash: "allow", mcp: "deny", question: "allow" }, }, - tags: ["debugging", "troubleshooting"], + category: "development", }, ] const EMPTY_METADATA: MarketplaceInstalledMetadata = { project: {}, global: {} } const PARTIAL_INSTALLED_SKILLS: MarketplaceInstalledMetadata = { - project: { "nextjs-developer": { type: "skill" } }, - global: { "python-data-science": { type: "skill" } }, + project: { "skill:nextjs-developer": { type: "skill" } }, + global: { "skill:python-data-science": { type: "skill" } }, } const PARTIAL_INSTALLED_MCPS: MarketplaceInstalledMetadata = { - project: { "github-mcp": { type: "mcp" } }, - global: { "postgres-mcp": { type: "mcp" } }, + project: { "mcp:github-mcp": { type: "mcp" } }, + global: { "mcp:postgres-mcp": { type: "mcp" } }, } const PARTIAL_INSTALLED_AGENTS: MarketplaceInstalledMetadata = { - project: { architect: { type: "agent" } }, - global: { reviewer: { type: "agent" } }, + project: { "agent:architect": { type: "agent" } }, + global: { "agent:reviewer": { type: "agent" } }, +} + +const PARTIAL_INSTALLED_MIXED: MarketplaceInstalledMetadata = { + project: { + "agent:architect": { type: "agent" }, + "mcp:github-mcp": { type: "mcp" }, + }, + global: { "skill:python-data-science": { type: "skill" } }, } const noop = () => {} @@ -292,8 +300,27 @@ const noop = () => {} // Stories // --------------------------------------------------------------------------- +export const MixedListWithItems: Story = { + name: "Mixed list — categories and installed items", + render: () => ( + +
+ +
+
+ ), +} + export const SkillsTabWithItems: Story = { - name: "Skills tab — with items", + name: "Skills list — with items", render: () => (
@@ -301,7 +328,6 @@ export const SkillsTabWithItems: Story = { items={MOCK_SKILLS} metadata={EMPTY_METADATA} fetching={false} - type="skill" searchPlaceholder="Search skills..." emptyMessage="No skills found" onInstall={noop} @@ -313,7 +339,7 @@ export const SkillsTabWithItems: Story = { } export const SkillsTabWithInstalled: Story = { - name: "Skills tab — some installed", + name: "Skills list — some installed", render: () => (
@@ -321,7 +347,6 @@ export const SkillsTabWithInstalled: Story = { items={MOCK_SKILLS} metadata={PARTIAL_INSTALLED_SKILLS} fetching={false} - type="skill" searchPlaceholder="Search skills..." emptyMessage="No skills found" onInstall={noop} @@ -333,7 +358,7 @@ export const SkillsTabWithInstalled: Story = { } export const SkillsTabEmpty: Story = { - name: "Skills tab — empty state", + name: "Skills list — empty state", render: () => (
@@ -341,7 +366,6 @@ export const SkillsTabEmpty: Story = { items={[]} metadata={EMPTY_METADATA} fetching={false} - type="skill" searchPlaceholder="Search skills..." emptyMessage="No skills found" onInstall={noop} @@ -393,7 +417,7 @@ export const InstalledSkillCard: Story = { // --------------------------------------------------------------------------- export const McpTabWithItems: Story = { - name: "MCP tab — with items", + name: "MCP list — with items", render: () => (
@@ -401,7 +425,6 @@ export const McpTabWithItems: Story = { items={MOCK_MCPS} metadata={EMPTY_METADATA} fetching={false} - type="mcp" searchPlaceholder="Search MCP servers..." emptyMessage="No MCP servers found" onInstall={noop} @@ -413,7 +436,7 @@ export const McpTabWithItems: Story = { } export const McpTabWithInstalled: Story = { - name: "MCP tab — some installed", + name: "MCP list — some installed", render: () => (
@@ -421,7 +444,6 @@ export const McpTabWithInstalled: Story = { items={MOCK_MCPS} metadata={PARTIAL_INSTALLED_MCPS} fetching={false} - type="mcp" searchPlaceholder="Search MCP servers..." emptyMessage="No MCP servers found" onInstall={noop} @@ -433,7 +455,7 @@ export const McpTabWithInstalled: Story = { } export const McpTabEmpty: Story = { - name: "MCP tab — empty state", + name: "MCP list — empty state", render: () => (
@@ -441,7 +463,6 @@ export const McpTabEmpty: Story = { items={[]} metadata={EMPTY_METADATA} fetching={false} - type="mcp" searchPlaceholder="Search MCP servers..." emptyMessage="No MCP servers found" onInstall={noop} @@ -491,7 +512,7 @@ export const InstalledMcpCard: Story = { // --------------------------------------------------------------------------- export const AgentsTabWithItems: Story = { - name: "Agents tab — with items", + name: "Agents list — with items", render: () => (
@@ -499,7 +520,6 @@ export const AgentsTabWithItems: Story = { items={MOCK_AGENTS} metadata={EMPTY_METADATA} fetching={false} - type="agent" searchPlaceholder="Search agents..." emptyMessage="No agents found" onInstall={noop} @@ -511,7 +531,7 @@ export const AgentsTabWithItems: Story = { } export const AgentsTabWithInstalled: Story = { - name: "Agents tab — some installed", + name: "Agents list — some installed", render: () => (
@@ -519,7 +539,6 @@ export const AgentsTabWithInstalled: Story = { items={MOCK_AGENTS} metadata={PARTIAL_INSTALLED_AGENTS} fetching={false} - type="agent" searchPlaceholder="Search agents..." emptyMessage="No agents found" onInstall={noop} @@ -531,7 +550,7 @@ export const AgentsTabWithInstalled: Story = { } export const AgentsTabEmpty: Story = { - name: "Agents tab — empty state", + name: "Agents list — empty state", render: () => (
@@ -539,7 +558,6 @@ export const AgentsTabEmpty: Story = { items={[]} metadata={EMPTY_METADATA} fetching={false} - type="agent" searchPlaceholder="Search agents..." emptyMessage="No agents found" onInstall={noop} diff --git a/packages/kilo-vscode/webview-ui/src/types/marketplace.ts b/packages/kilo-vscode/webview-ui/src/types/marketplace.ts index 27f83a2b782..954c4b6c159 100644 --- a/packages/kilo-vscode/webview-ui/src/types/marketplace.ts +++ b/packages/kilo-vscode/webview-ui/src/types/marketplace.ts @@ -16,9 +16,9 @@ export interface MarketplaceItemBase { id: string name: string description: string + category: string author?: string authorUrl?: string - tags?: string[] prerequisites?: string[] } @@ -44,7 +44,6 @@ export interface AgentMarketplaceItem extends MarketplaceItemBase { export interface SkillMarketplaceItem extends MarketplaceItemBase { type: "skill" - category: string githubUrl: string content: string displayName: string @@ -66,5 +65,5 @@ export interface MarketplaceInstalledMetadata { export interface MarketplaceFilters { type?: string search?: string - tags?: string[] + categories?: string[] } From 455848d46b64a4052422a3ab2d68bfd2bf68baf7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 23 Jun 2026 11:46:12 +0000 Subject: [PATCH 2/9] chore: update kilo-vscode visual regression baselines --- .../marketplace/agents-tab-empty-chromium-linux.png | 4 ++-- .../marketplace/agents-tab-with-installed-chromium-linux.png | 4 ++-- .../marketplace/agents-tab-with-items-chromium-linux.png | 4 ++-- .../marketplace/mcp-tab-empty-chromium-linux.png | 4 ++-- .../marketplace/mcp-tab-with-installed-chromium-linux.png | 4 ++-- .../marketplace/mcp-tab-with-items-chromium-linux.png | 4 ++-- .../marketplace/mixed-list-with-items-chromium-linux.png | 3 +++ .../marketplace/skills-tab-with-installed-chromium-linux.png | 4 ++-- .../marketplace/skills-tab-with-items-chromium-linux.png | 4 ++-- 9 files changed, 19 insertions(+), 16 deletions(-) create mode 100644 packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mixed-list-with-items-chromium-linux.png diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-empty-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-empty-chromium-linux.png index fd503e50e61..caa3cb9437a 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-empty-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-empty-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7338febdf802b5ce501773be164e9da152fb7903e9b16807bd8751fa38f208ed -size 10310 +oid sha256:c73b6d01ced180f3a6175159b0b219c38fb7be40093ca6bbfcf7fda83d19ab2b +size 10365 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-with-installed-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-with-installed-chromium-linux.png index 071927ae44d..06dcbee6bbd 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-with-installed-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-with-installed-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7e075b4961d3a62c8deda6b481f64cb8777d05183a9c10d0dd2373b7972d3ab -size 53678 +oid sha256:1e671d8d2ed33d57b30c5dc6d5f704cdc005bb545cda472e2b15c3ebd504654d +size 54302 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-with-items-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-with-items-chromium-linux.png index 158e7e0bdcc..2ee5f00f21c 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-with-items-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-with-items-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b0dd1dc26b28d354ecd14c53dd1b9e6158a161f47152f30299ba4c0827ce1674 -size 50904 +oid sha256:58b200204269bee41139d86dc506a71a57c6589e9e95e6e2b4c651a0ba2779df +size 51429 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-empty-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-empty-chromium-linux.png index e6db4c0c7e4..45809309bec 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-empty-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-empty-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1edca81fb2c946f87df5436684418e63b7a9c761079e44f024971a81316b8263 -size 6549 +oid sha256:532ce502f99b7f88d6b01bedbb8cbda01adb3abdbf0886f1f67b85212de78e35 +size 11008 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-with-installed-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-with-installed-chromium-linux.png index 59da02c6806..acfb685a306 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-with-installed-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-with-installed-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce4ba314abb23bb43694d512ade273d99760f250b061e235899d86e170bb5f9a -size 52892 +oid sha256:442822e2b6ca1f759c18c17e0dad396333ba9eff4d0ceaeb836d7b8e9b484977 +size 57102 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-with-items-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-with-items-chromium-linux.png index 62f2ae9b606..a50276b45b6 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-with-items-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-with-items-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:631d41ecf6216249943722a797f75c3aefb2f84ebdb7ee80529662a26fa35af5 -size 50206 +oid sha256:ad0d1aa853a03f280a20074c2233e85857c2d644182d0efb8484d4fb0ce8f00e +size 54311 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mixed-list-with-items-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mixed-list-with-items-chromium-linux.png new file mode 100644 index 00000000000..d7bf6d29366 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mixed-list-with-items-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:099bfba94c6b1e9a345e404fc658b0e60a8fafbc3d4724a7757b618a83f37f40 +size 52577 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-with-installed-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-with-installed-chromium-linux.png index d304537721d..133c7280543 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-with-installed-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-with-installed-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c35bc13f78745ab41bf39e732c3945578fc2cb2cc47ef231e51b1357bc0b2817 -size 54160 +oid sha256:d09dfc5fc3adf5d8ea92f011af46cdcc750a4a1880463bda13c7cd9c3b961843 +size 56181 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-with-items-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-with-items-chromium-linux.png index 0bb9c058098..51b6ba8d1b0 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-with-items-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-with-items-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:52c6eeef2d393b159fe9e88a9d283308d969b7311fe68ea23667561d21a2ed87 -size 51338 +oid sha256:800271cd7f29ba5e332538ad683215e825b62c5a0676b73b3f081aec7da4ae1b +size 53283 From 34540a959f896c7100e3dc8893727e889fc6c67a Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 24 Jun 2026 08:21:35 +0200 Subject: [PATCH 3/9] feat(vscode): add marketplace type filters --- .../tests/unit/marketplace-actions.test.ts | 9 +++-- .../src/components/marketplace/ItemCard.tsx | 2 +- .../marketplace/MarketplaceListView.tsx | 40 ++++++++++++++++++- .../components/marketplace/marketplace.css | 29 +++++++++++++- .../src/components/marketplace/utils.ts | 26 +++++++----- 5 files changed, 90 insertions(+), 16 deletions(-) diff --git a/packages/kilo-vscode/tests/unit/marketplace-actions.test.ts b/packages/kilo-vscode/tests/unit/marketplace-actions.test.ts index aacc681a9ff..c305e05c047 100644 --- a/packages/kilo-vscode/tests/unit/marketplace-actions.test.ts +++ b/packages/kilo-vscode/tests/unit/marketplace-actions.test.ts @@ -113,9 +113,12 @@ describe("Marketplace installation metadata", () => { ] const metadata = { project: { "mcp:warehouse": { type: "mcp" } }, global: {} } - expect(filterItems(items, metadata, "reviewer", "all", []).map((item) => item.id)).toEqual(["reviewer"]) - expect(filterItems(items, metadata, "", "all", ["business"]).map((item) => item.id)).toEqual(["campaign-writer"]) - expect(filterItems(items, metadata, "", "installed", []).map((item) => item.id)).toEqual(["warehouse"]) + expect(filterItems(items, metadata, "reviewer", "all", [], []).map((item) => item.id)).toEqual(["reviewer"]) + expect(filterItems(items, metadata, "", "all", ["business"], []).map((item) => item.id)).toEqual([ + "campaign-writer", + ]) + expect(filterItems(items, metadata, "", "installed", [], []).map((item) => item.id)).toEqual(["warehouse"]) + expect(filterItems(items, metadata, "", "all", [], ["mcp"]).map((item) => item.id)).toEqual(["warehouse"]) }) }) diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/ItemCard.tsx b/packages/kilo-vscode/webview-ui/src/components/marketplace/ItemCard.tsx index c82127b96d1..bfb4a9aaf8d 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/ItemCard.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/ItemCard.tsx @@ -49,7 +49,7 @@ export const ItemCard = (props: Props) => { {name()} - {type()} + {type()}
diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceListView.tsx b/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceListView.tsx index 4bbba1f7e03..6142c6c94ed 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceListView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceListView.tsx @@ -33,6 +33,7 @@ export const MarketplaceListView = (props: Props) => { const { t } = useLanguage() const [search, setSearch] = createSignal("") const [status, setStatus] = createSignal({ value: "all", label: t("marketplace.filter.all") }) + const [types, setTypes] = createSignal([]) const [categories, setCategories] = createSignal([]) const options = (): StatusOption[] => [ @@ -47,8 +48,18 @@ export const MarketplaceListView = (props: Props) => { .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(" ") + const allTypes = createMemo(() => { + const available = new Set(props.items.map((item) => item.type)) + return (["agent", "mcp", "skill"] as const).filter((type) => available.has(type)) + }) const allCategories = createMemo(() => Array.from(new Set(props.items.map((item) => item.category))).sort()) + const typeLabel = (type: MarketplaceItem["type"]) => { + if (type === "mcp") return t("marketplace.badge.mcpServer") + if (type === "agent") return t("marketplace.remove.type.agent") + return t("marketplace.remove.type.skill") + } + createEffect(() => { const available = new Set(allCategories()) setCategories((current) => { @@ -57,6 +68,15 @@ export const MarketplaceListView = (props: Props) => { }) }) + const toggleType = (type: MarketplaceItem["type"]) => { + const current = types() + if (current.includes(type)) { + setTypes(current.filter((value) => value !== type)) + return + } + setTypes([...current, type]) + } + const toggleCategory = (category: string) => { const current = categories() if (current.includes(category)) { @@ -66,7 +86,9 @@ export const MarketplaceListView = (props: Props) => { setCategories([...current, category]) } - const filtered = createMemo(() => filterItems(props.items, props.metadata, search(), status().value, categories())) + const filtered = createMemo(() => + filterItems(props.items, props.metadata, search(), status().value, categories(), types()), + ) return (
@@ -82,6 +104,22 @@ export const MarketplaceListView = (props: Props) => { onSelect={(v: StatusOption | undefined) => v && setStatus(v)} />
+ 1}> +
+ + {(type) => ( + + )} + +
+
0}>
diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css b/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css index 45beaca8006..fdcdacb753b 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css @@ -27,14 +27,16 @@ flex: 1; } -/* Category filters */ +/* Type and category filters */ +.marketplace-types, .marketplace-categories { display: flex; flex-wrap: wrap; gap: 4px; } +.marketplace-type-filter, .marketplace-category-filter { all: unset; cursor: pointer; @@ -42,15 +44,18 @@ transition: opacity 0.15s ease; } +.marketplace-type-filter:hover, .marketplace-category-filter:hover { opacity: 0.8; } +.marketplace-type-filter:focus-visible, .marketplace-category-filter:focus-visible { outline: 1px solid var(--vscode-focusBorder); outline-offset: 2px; } +.marketplace-type-filter.active, .marketplace-category-filter.active { opacity: 1; } @@ -200,6 +205,28 @@ .marketplace-badge-type { flex-shrink: 0; + letter-spacing: 0.4px; +} + +.marketplace-type-agent [data-component="tag"], +.marketplace-badge-type.marketplace-type-agent { + color: var(--text-on-success-base) !important; + background-color: color-mix(in srgb, var(--border-success-base) 14%, transparent) !important; + border-color: var(--border-success-base) !important; +} + +.marketplace-type-mcp [data-component="tag"], +.marketplace-badge-type.marketplace-type-mcp { + color: var(--text-on-info-base) !important; + background-color: color-mix(in srgb, var(--border-info-base) 14%, transparent) !important; + border-color: var(--border-info-base) !important; +} + +.marketplace-type-skill [data-component="tag"], +.marketplace-badge-type.marketplace-type-skill { + color: var(--text-on-warning-base) !important; + background-color: color-mix(in srgb, var(--border-warning-base) 14%, transparent) !important; + border-color: var(--border-warning-base) !important; } .marketplace-badge-installed { diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts b/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts index 66a7910a2aa..6d9e6f0f9c3 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts @@ -20,30 +20,36 @@ export function installedScopes( return scopes } +function matches(item: MarketplaceItem, query: string) { + const skill = item.type === "skill" ? item : undefined + return ( + item.id.toLowerCase().includes(query) || + item.name.toLowerCase().includes(query) || + item.description.toLowerCase().includes(query) || + item.category.toLowerCase().includes(query) || + item.type.includes(query) || + (item.author?.toLowerCase().includes(query) ?? false) || + (skill?.displayName.toLowerCase().includes(query) ?? false) + ) +} + export function filterItems( items: MarketplaceItem[], metadata: MarketplaceInstalledMetadata, search: string, status: string, categories: string[], + types: MarketplaceItem["type"][], ): MarketplaceItem[] { const query = search.trim().toLowerCase() return items .filter((item) => { if (status === "installed" && !isInstalled(item.id, item.type, metadata)) return false if (status === "notInstalled" && isInstalled(item.id, item.type, metadata)) return false + if (types.length > 0 && !types.includes(item.type)) return false if (categories.length > 0 && !categories.includes(item.category)) return false if (!query) return true - const skill = item.type === "skill" ? item : undefined - return ( - item.id.toLowerCase().includes(query) || - item.name.toLowerCase().includes(query) || - item.description.toLowerCase().includes(query) || - item.category.toLowerCase().includes(query) || - item.type.includes(query) || - (item.author?.toLowerCase().includes(query) ?? false) || - (skill?.displayName.toLowerCase().includes(query) ?? false) - ) + return matches(item, query) }) .sort((a, b) => a.name.localeCompare(b.name)) } From cf07116fde14f97622cee65a1612d58517951ebb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 24 Jun 2026 06:24:20 +0000 Subject: [PATCH 4/9] chore: update kilo-vscode visual regression baselines --- .../marketplace/mixed-list-with-items-chromium-linux.png | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mixed-list-with-items-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mixed-list-with-items-chromium-linux.png index d7bf6d29366..9556137ca56 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mixed-list-with-items-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mixed-list-with-items-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:099bfba94c6b1e9a345e404fc658b0e60a8fafbc3d4724a7757b618a83f37f40 -size 52577 +oid sha256:4a7bcef3102838812625393f0cead3a10067ae32e67dd3f174d0e74d03116c8c +size 54735 From 1568a43e602ae052678cb98f4cd52a5f901ee92c Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 24 Jun 2026 11:56:18 +0200 Subject: [PATCH 5/9] test(vscode): trim marketplace visual coverage --- .../agents-tab-empty-chromium-linux.png | 3 - ...ents-tab-with-installed-chromium-linux.png | 3 - .../agents-tab-with-items-chromium-linux.png | 3 - .../mcp-tab-empty-chromium-linux.png | 3 - .../mcp-tab-with-installed-chromium-linux.png | 3 - .../mcp-tab-with-items-chromium-linux.png | 3 - .../skills-tab-empty-chromium-linux.png | 3 - ...ills-tab-with-installed-chromium-linux.png | 3 - .../skills-tab-with-items-chromium-linux.png | 3 - .../kilo-vscode/tests/accessibility.spec.ts | 3 +- .../src/stories/marketplace.stories.tsx | 162 +----------------- 11 files changed, 6 insertions(+), 186 deletions(-) delete mode 100644 packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-empty-chromium-linux.png delete mode 100644 packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-with-installed-chromium-linux.png delete mode 100644 packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-with-items-chromium-linux.png delete mode 100644 packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-empty-chromium-linux.png delete mode 100644 packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-with-installed-chromium-linux.png delete mode 100644 packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-with-items-chromium-linux.png delete mode 100644 packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-empty-chromium-linux.png delete mode 100644 packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-with-installed-chromium-linux.png delete mode 100644 packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-with-items-chromium-linux.png diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-empty-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-empty-chromium-linux.png deleted file mode 100644 index caa3cb9437a..00000000000 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-empty-chromium-linux.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c73b6d01ced180f3a6175159b0b219c38fb7be40093ca6bbfcf7fda83d19ab2b -size 10365 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-with-installed-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-with-installed-chromium-linux.png deleted file mode 100644 index 06dcbee6bbd..00000000000 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-with-installed-chromium-linux.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1e671d8d2ed33d57b30c5dc6d5f704cdc005bb545cda472e2b15c3ebd504654d -size 54302 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-with-items-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-with-items-chromium-linux.png deleted file mode 100644 index 2ee5f00f21c..00000000000 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/agents-tab-with-items-chromium-linux.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:58b200204269bee41139d86dc506a71a57c6589e9e95e6e2b4c651a0ba2779df -size 51429 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-empty-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-empty-chromium-linux.png deleted file mode 100644 index 45809309bec..00000000000 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-empty-chromium-linux.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:532ce502f99b7f88d6b01bedbb8cbda01adb3abdbf0886f1f67b85212de78e35 -size 11008 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-with-installed-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-with-installed-chromium-linux.png deleted file mode 100644 index acfb685a306..00000000000 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-with-installed-chromium-linux.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:442822e2b6ca1f759c18c17e0dad396333ba9eff4d0ceaeb836d7b8e9b484977 -size 57102 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-with-items-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-with-items-chromium-linux.png deleted file mode 100644 index a50276b45b6..00000000000 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/mcp-tab-with-items-chromium-linux.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ad0d1aa853a03f280a20074c2233e85857c2d644182d0efb8484d4fb0ce8f00e -size 54311 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-empty-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-empty-chromium-linux.png deleted file mode 100644 index 9903c56a8c5..00000000000 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-empty-chromium-linux.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9f722d2782f69170a3efca6ed64ab02d9140f845bb47059ba193e238c768d0a5 -size 4852 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-with-installed-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-with-installed-chromium-linux.png deleted file mode 100644 index 133c7280543..00000000000 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-with-installed-chromium-linux.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d09dfc5fc3adf5d8ea92f011af46cdcc750a4a1880463bda13c7cd9c3b961843 -size 56181 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-with-items-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-with-items-chromium-linux.png deleted file mode 100644 index 51b6ba8d1b0..00000000000 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/skills-tab-with-items-chromium-linux.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:800271cd7f29ba5e332538ad683215e825b62c5a0676b73b3f081aec7da4ae1b -size 53283 diff --git a/packages/kilo-vscode/tests/accessibility.spec.ts b/packages/kilo-vscode/tests/accessibility.spec.ts index fead3c81f38..153eeb1c34e 100644 --- a/packages/kilo-vscode/tests/accessibility.spec.ts +++ b/packages/kilo-vscode/tests/accessibility.spec.ts @@ -11,8 +11,7 @@ const STORIES = [ { id: "profile--logged-in-personal", name: "Profile / personal account" }, { id: "profile--logged-in", name: "Profile / organization account" }, { id: "settings--providers-configure", name: "Settings / providers empty state" }, - { id: "marketplace--skills-tab-empty", name: "Marketplace / skills empty state" }, - { id: "marketplace--agents-tab-empty", name: "Marketplace / agents empty state" }, + { id: "marketplace--empty-list", name: "Marketplace / empty state" }, { id: "agentmanager--sidebar-search-open", name: "Agent Manager / sidebar search" }, ] diff --git a/packages/kilo-vscode/webview-ui/src/stories/marketplace.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/marketplace.stories.tsx index 498bac128b0..b8c0d692b67 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/marketplace.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/marketplace.stories.tsx @@ -319,55 +319,17 @@ export const MixedListWithItems: Story = { ), } -export const SkillsTabWithItems: Story = { - name: "Skills list — with items", +export const EmptyList: Story = { + name: "Mixed list — empty state", render: () => ( -
- -
-
- ), -} - -export const SkillsTabWithInstalled: Story = { - name: "Skills list — some installed", - render: () => ( - -
- -
-
- ), -} - -export const SkillsTabEmpty: Story = { - name: "Skills list — empty state", - render: () => ( - -
+
@@ -416,63 +378,6 @@ export const InstalledSkillCard: Story = { // MCP Stories // --------------------------------------------------------------------------- -export const McpTabWithItems: Story = { - name: "MCP list — with items", - render: () => ( - -
- -
-
- ), -} - -export const McpTabWithInstalled: Story = { - name: "MCP list — some installed", - render: () => ( - -
- -
-
- ), -} - -export const McpTabEmpty: Story = { - name: "MCP list — empty state", - render: () => ( - -
- -
-
- ), -} - export const SingleMcpCard: Story = { name: "ItemCard — single MCP not installed", render: () => ( @@ -511,63 +416,6 @@ export const InstalledMcpCard: Story = { // Mode Stories // --------------------------------------------------------------------------- -export const AgentsTabWithItems: Story = { - name: "Agents list — with items", - render: () => ( - -
- -
-
- ), -} - -export const AgentsTabWithInstalled: Story = { - name: "Agents list — some installed", - render: () => ( - -
- -
-
- ), -} - -export const AgentsTabEmpty: Story = { - name: "Agents list — empty state", - render: () => ( - -
- -
-
- ), -} - export const SingleAgentCard: Story = { name: "ItemCard — single agent not installed", render: () => ( From 47dfd5e68883cff27f50068c7c91d20560d7e07e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 24 Jun 2026 09:59:06 +0000 Subject: [PATCH 6/9] chore: update kilo-vscode visual regression baselines --- .../marketplace/empty-list-chromium-linux.png | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/empty-list-chromium-linux.png diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/empty-list-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/empty-list-chromium-linux.png new file mode 100644 index 00000000000..9bf8b91cdb8 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/marketplace/empty-list-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6eb9d7670f55c5529dce90d11fceb5c9da7a0d3a5f7270fdfd27e66ca1f3d72f +size 9851 From ebcf81f58f7a91f51780c83cf1fe67ae2712254a Mon Sep 17 00:00:00 2001 From: markijbema <624143+markijbema@users.noreply.github.com> Date: Wed, 24 Jun 2026 10:03:28 +0000 Subject: [PATCH 7/9] fix(vscode): match marketplace display labels in search Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --- .../kilo-vscode/tests/unit/marketplace-actions.test.ts | 6 +++++- .../src/components/marketplace/MarketplaceListView.tsx | 6 +++++- .../webview-ui/src/components/marketplace/utils.ts | 8 +++++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/kilo-vscode/tests/unit/marketplace-actions.test.ts b/packages/kilo-vscode/tests/unit/marketplace-actions.test.ts index c305e05c047..0a12943fb33 100644 --- a/packages/kilo-vscode/tests/unit/marketplace-actions.test.ts +++ b/packages/kilo-vscode/tests/unit/marketplace-actions.test.ts @@ -95,7 +95,7 @@ describe("Marketplace installation metadata", () => { id: "warehouse", name: "Warehouse", description: "Queries data", - category: "data", + category: "web-automation", url: "https://example.com", content: "{}", }, @@ -114,6 +114,10 @@ describe("Marketplace installation metadata", () => { const metadata = { project: { "mcp:warehouse": { type: "mcp" } }, global: {} } expect(filterItems(items, metadata, "reviewer", "all", [], []).map((item) => item.id)).toEqual(["reviewer"]) + expect(filterItems(items, metadata, "web automation", "all", [], []).map((item) => item.id)).toEqual(["warehouse"]) + expect( + filterItems(items, metadata, "servidor mcp", "all", [], [], { mcp: "Servidor MCP" }).map((item) => item.id), + ).toEqual(["warehouse"]) expect(filterItems(items, metadata, "", "all", ["business"], []).map((item) => item.id)).toEqual([ "campaign-writer", ]) diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceListView.tsx b/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceListView.tsx index 6142c6c94ed..9fa87889057 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceListView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceListView.tsx @@ -87,7 +87,11 @@ export const MarketplaceListView = (props: Props) => { } const filtered = createMemo(() => - filterItems(props.items, props.metadata, search(), status().value, categories(), types()), + filterItems(props.items, props.metadata, search(), status().value, categories(), types(), { + agent: typeLabel("agent"), + mcp: typeLabel("mcp"), + skill: typeLabel("skill"), + }), ) return ( diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts b/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts index 6d9e6f0f9c3..be2e93948a8 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts @@ -20,14 +20,15 @@ export function installedScopes( return scopes } -function matches(item: MarketplaceItem, query: string) { +function matches(item: MarketplaceItem, query: string, labels: Partial>) { const skill = item.type === "skill" ? item : undefined return ( item.id.toLowerCase().includes(query) || item.name.toLowerCase().includes(query) || item.description.toLowerCase().includes(query) || - item.category.toLowerCase().includes(query) || + item.category.replaceAll("-", " ").toLowerCase().includes(query) || item.type.includes(query) || + (labels[item.type]?.toLowerCase().includes(query) ?? false) || (item.author?.toLowerCase().includes(query) ?? false) || (skill?.displayName.toLowerCase().includes(query) ?? false) ) @@ -40,6 +41,7 @@ export function filterItems( status: string, categories: string[], types: MarketplaceItem["type"][], + labels: Partial> = {}, ): MarketplaceItem[] { const query = search.trim().toLowerCase() return items @@ -49,7 +51,7 @@ export function filterItems( if (types.length > 0 && !types.includes(item.type)) return false if (categories.length > 0 && !categories.includes(item.category)) return false if (!query) return true - return matches(item, query) + return matches(item, query, labels) }) .sort((a, b) => a.name.localeCompare(b.name)) } From 233fb0d126ee38368a71a1c2c0b02851a6f68ed3 Mon Sep 17 00:00:00 2001 From: markijbema <624143+markijbema@users.noreply.github.com> Date: Wed, 24 Jun 2026 10:03:52 +0000 Subject: [PATCH 8/9] fix(vscode): expose type filters in high contrast Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --- .../webview-ui/src/components/marketplace/marketplace.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css b/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css index fdcdacb753b..e5f8cdcb8b8 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css @@ -345,10 +345,12 @@ body.vscode-high-contrast-light { border: 1px solid var(--vscode-contrastBorder, transparent); } + .marketplace-type-filter, .marketplace-category-filter { border: 1px solid var(--vscode-contrastBorder, transparent); } + .marketplace-type-filter.active, .marketplace-category-filter.active { border-color: var(--vscode-contrastActiveBorder, var(--vscode-focusBorder)); } From 588b8c899690581bc69d0bed83305b8d598ed1a3 Mon Sep 17 00:00:00 2001 From: markijbema <624143+markijbema@users.noreply.github.com> Date: Wed, 24 Jun 2026 10:07:38 +0000 Subject: [PATCH 9/9] fix(vscode): prune unavailable marketplace filters Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --- .../kilo-vscode/tests/unit/marketplace-actions.test.ts | 6 +++++- .../src/components/marketplace/MarketplaceListView.tsx | 9 +++------ .../webview-ui/src/components/marketplace/utils.ts | 6 ++++++ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/kilo-vscode/tests/unit/marketplace-actions.test.ts b/packages/kilo-vscode/tests/unit/marketplace-actions.test.ts index 0a12943fb33..f778f9b1f0f 100644 --- a/packages/kilo-vscode/tests/unit/marketplace-actions.test.ts +++ b/packages/kilo-vscode/tests/unit/marketplace-actions.test.ts @@ -7,7 +7,7 @@ import { type MarketplaceRemoveContext, } from "../../src/services/marketplace/actions" import type { McpMarketplaceItem } from "../../src/services/marketplace/types" -import { filterItems, installedScopes } from "../../webview-ui/src/components/marketplace/utils" +import { filterItems, installedScopes, retain } from "../../webview-ui/src/components/marketplace/utils" import type { MarketplaceItem } from "../../webview-ui/src/types/marketplace" const project = "/repo" @@ -80,6 +80,10 @@ describe("Marketplace installation metadata", () => { expect(installedScopes("dbt", "agent", metadata)).toEqual([]) }) + it("removes filters that are no longer available", () => { + expect(retain(["agent", "mcp"], ["mcp", "skill"])).toEqual(["mcp"]) + }) + it("filters the mixed list by search, category, and status", () => { const items: MarketplaceItem[] = [ { diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceListView.tsx b/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceListView.tsx index 9fa87889057..c3fe6136a65 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceListView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/MarketplaceListView.tsx @@ -10,7 +10,7 @@ import type { MarketplaceInstalledMetadata, } from "../../types/marketplace" import { useLanguage } from "../../context/language" -import { filterItems } from "./utils" +import { filterItems, retain } from "./utils" import { ItemCard } from "./ItemCard" import { MarketplaceContribute } from "./MarketplaceContribute" @@ -61,11 +61,8 @@ export const MarketplaceListView = (props: Props) => { } createEffect(() => { - const available = new Set(allCategories()) - setCategories((current) => { - const next = current.filter((category) => available.has(category)) - return next.length === current.length ? current : next - }) + setTypes((current) => retain(current, allTypes())) + setCategories((current) => retain(current, allCategories())) }) const toggleType = (type: MarketplaceItem["type"]) => { diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts b/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts index be2e93948a8..0f616cc0c42 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/utils.ts @@ -1,5 +1,11 @@ import type { MarketplaceInstalledMetadata, MarketplaceItem } from "../../types/marketplace" +export function retain(selected: T[], available: T[]): T[] { + const values = new Set(available) + const next = selected.filter((value) => values.has(value)) + return next.length === selected.length ? selected : next +} + export function isInstalled( id: string, type: string,