@burdenoff/microfe-vibecontrols 2026.528.6 → 2026.528.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,287 @@
1
+ import { useTr as e } from "../shared/hooks/useTr.js";
2
+ import { PLUGIN_CATALOG as t } from "../constants/pluginCatalog.js";
3
+ import { useCallback as n, useMemo as r, useState as i } from "react";
4
+ import { Check as a, ChevronDown as o, Copy as s, Package as c, Plug as l, Search as u, X as d } from "lucide-react";
5
+ import { jsx as f, jsxs as p } from "react/jsx-runtime";
6
+ import { Card as m, PageContainer as h } from "@burdenoff/fe-libs/ui";
7
+ import { MotionPage as g } from "@burdenoff/fe-libs/shared/motion";
8
+ //#region src/pages/PluginsCatalogPage.tsx
9
+ var _ = [
10
+ {
11
+ value: "all",
12
+ labelKey: "vibecontrols.plugins.category.all",
13
+ labelFallback: "All"
14
+ },
15
+ {
16
+ value: "ai-core",
17
+ labelKey: "vibecontrols.plugins.category.aiCore",
18
+ labelFallback: "AI Core"
19
+ },
20
+ {
21
+ value: "ai-provider",
22
+ labelKey: "vibecontrols.plugins.category.aiProvider",
23
+ labelFallback: "AI Providers"
24
+ },
25
+ {
26
+ value: "plan-core",
27
+ labelKey: "vibecontrols.plugins.category.planCore",
28
+ labelFallback: "Plan Core"
29
+ },
30
+ {
31
+ value: "plan-provider",
32
+ labelKey: "vibecontrols.plugins.category.planProvider",
33
+ labelFallback: "Plan Providers"
34
+ },
35
+ {
36
+ value: "session-core",
37
+ labelKey: "vibecontrols.plugins.category.sessionCore",
38
+ labelFallback: "Session Core"
39
+ },
40
+ {
41
+ value: "session-provider",
42
+ labelKey: "vibecontrols.plugins.category.sessionProvider",
43
+ labelFallback: "Session Providers"
44
+ },
45
+ {
46
+ value: "tunnel",
47
+ labelKey: "vibecontrols.plugins.category.tunnel",
48
+ labelFallback: "Tunnel"
49
+ },
50
+ {
51
+ value: "vcs",
52
+ labelKey: "vibecontrols.plugins.category.vcs",
53
+ labelFallback: "VCS"
54
+ },
55
+ {
56
+ value: "api-playground",
57
+ labelKey: "vibecontrols.plugins.category.apiPlayground",
58
+ labelFallback: "API Playground"
59
+ },
60
+ {
61
+ value: "gitops-core",
62
+ labelKey: "vibecontrols.plugins.category.gitopsCore",
63
+ labelFallback: "GitOps Core"
64
+ },
65
+ {
66
+ value: "gitops-provider",
67
+ labelKey: "vibecontrols.plugins.category.gitopsProvider",
68
+ labelFallback: "GitOps Providers"
69
+ },
70
+ {
71
+ value: "security-core",
72
+ labelKey: "vibecontrols.plugins.category.securityCore",
73
+ labelFallback: "Security Core"
74
+ },
75
+ {
76
+ value: "security-provider",
77
+ labelKey: "vibecontrols.plugins.category.securityProvider",
78
+ labelFallback: "Security Providers"
79
+ },
80
+ {
81
+ value: "tool",
82
+ labelKey: "vibecontrols.plugins.category.tool",
83
+ labelFallback: "Tools"
84
+ }
85
+ ];
86
+ function v(e) {
87
+ return e.endsWith("-core") ? "Meta" : e.endsWith("-provider") ? "Provider" : "Plugin";
88
+ }
89
+ function y(e) {
90
+ switch (e) {
91
+ case "Meta": return "bg-accent-purple-subtle text-accent-purple border-accent-purple/30";
92
+ case "Provider": return "bg-status-info-bg text-status-info-text border-status-info-border";
93
+ default: return "bg-bg-sunken text-text-secondary border-border-default";
94
+ }
95
+ }
96
+ function b({ text: e, ariaLabel: t }) {
97
+ let [r, o] = i(!1);
98
+ return /* @__PURE__ */ f("button", {
99
+ type: "button",
100
+ onClick: n(async () => {
101
+ try {
102
+ typeof navigator < "u" && navigator.clipboard && await navigator.clipboard.writeText(e), o(!0), window.setTimeout(() => o(!1), 1500);
103
+ } catch {
104
+ o(!1);
105
+ }
106
+ }, [e]),
107
+ "aria-label": t,
108
+ className: "inline-flex items-center justify-center size-7 rounded border border-border-default bg-bg-surface text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors flex-shrink-0",
109
+ children: f(r ? a : s, { className: "size-3.5" })
110
+ });
111
+ }
112
+ function x({ plugin: t }) {
113
+ let n = e(), r = v(t.category), a = `npm install ${t.packageName}`, s = `import plugin from '${t.packageName}';`, [c, l] = i(!1), [u, d] = i(!1);
114
+ return /* @__PURE__ */ f(m, {
115
+ className: "flex flex-col h-full p-0 bg-bg-surface border-border-default",
116
+ children: /* @__PURE__ */ p("div", {
117
+ className: "flex flex-col gap-3 p-4",
118
+ children: [
119
+ /* @__PURE__ */ p("div", {
120
+ className: "flex items-start justify-between gap-3",
121
+ children: [/* @__PURE__ */ p("div", {
122
+ className: "min-w-0 flex-1",
123
+ children: [/* @__PURE__ */ f("h3", {
124
+ className: "text-base font-semibold text-text-primary truncate",
125
+ children: t.displayName
126
+ }), /* @__PURE__ */ f("p", {
127
+ className: "text-xs text-text-secondary font-mono truncate mt-1",
128
+ children: t.packageName
129
+ })]
130
+ }), /* @__PURE__ */ f("span", {
131
+ className: `inline-flex items-center gap-1 px-2 py-0.5 text-[10px] uppercase tracking-wide font-semibold rounded-full border ${y(r)} flex-shrink-0`,
132
+ children: r
133
+ })]
134
+ }),
135
+ /* @__PURE__ */ f("p", {
136
+ className: "text-sm text-text-secondary leading-snug",
137
+ children: t.description
138
+ }),
139
+ /* @__PURE__ */ p("div", {
140
+ className: "mt-1 space-y-1.5",
141
+ children: [/* @__PURE__ */ f("p", {
142
+ className: "text-[11px] font-medium uppercase tracking-wide text-text-secondary",
143
+ children: n("vibecontrols.plugins.install", "Install")
144
+ }), /* @__PURE__ */ p("div", {
145
+ className: "flex items-center gap-2 rounded-md border border-border-default bg-bg-sunken px-2.5 py-1.5",
146
+ children: [/* @__PURE__ */ f("code", {
147
+ className: "flex-1 text-xs font-mono text-text-primary truncate",
148
+ children: a
149
+ }), /* @__PURE__ */ f(b, {
150
+ text: a,
151
+ ariaLabel: n("vibecontrols.plugins.copyInstall", "Copy install command for {{name}}", { name: t.displayName })
152
+ })]
153
+ })]
154
+ }),
155
+ /* @__PURE__ */ p("div", {
156
+ className: "space-y-2",
157
+ children: [/* @__PURE__ */ p("button", {
158
+ type: "button",
159
+ onClick: () => l((e) => !e),
160
+ className: "w-full flex items-center justify-between gap-2 text-left text-xs font-medium text-text-secondary hover:text-text-primary transition-colors",
161
+ "aria-expanded": c,
162
+ children: [/* @__PURE__ */ f("span", { children: n("vibecontrols.plugins.usage", "Usage") }), /* @__PURE__ */ f(o, { className: `size-3.5 transition-transform ${c ? "rotate-180" : ""}` })]
163
+ }), c && /* @__PURE__ */ p("div", {
164
+ className: "flex items-start gap-2 rounded-md border border-border-default bg-bg-sunken px-2.5 py-2",
165
+ children: [/* @__PURE__ */ f("code", {
166
+ className: "flex-1 text-xs font-mono text-text-primary whitespace-pre-wrap break-all",
167
+ children: s
168
+ }), /* @__PURE__ */ f(b, {
169
+ text: s,
170
+ ariaLabel: n("vibecontrols.plugins.copyUsage", "Copy usage snippet for {{name}}", { name: t.displayName })
171
+ })]
172
+ })]
173
+ }),
174
+ /* @__PURE__ */ p("div", {
175
+ className: "space-y-2",
176
+ children: [/* @__PURE__ */ p("button", {
177
+ type: "button",
178
+ onClick: () => d((e) => !e),
179
+ className: "w-full flex items-center justify-between gap-2 text-left text-xs font-medium text-text-secondary hover:text-text-primary transition-colors",
180
+ "aria-expanded": u,
181
+ children: [/* @__PURE__ */ f("span", { children: n("vibecontrols.plugins.commands", "Command reference") }), /* @__PURE__ */ f(o, { className: `size-3.5 transition-transform ${u ? "rotate-180" : ""}` })]
182
+ }), u && /* @__PURE__ */ f("div", {
183
+ className: "rounded-md border border-border-default bg-bg-sunken px-2.5 py-2",
184
+ children: /* @__PURE__ */ f("p", {
185
+ className: "text-xs text-text-secondary",
186
+ children: n("vibecontrols.plugins.commandsHint", "See the agent admin sub-panel or the plugin README for the documented command list.")
187
+ })
188
+ })]
189
+ })
190
+ ]
191
+ })
192
+ });
193
+ }
194
+ function S() {
195
+ let n = e(), [a, o] = i(""), [s, m] = i("all"), v = r(() => Object.values(t).sort((e, t) => e.displayName.localeCompare(t.displayName)), []), y = r(() => {
196
+ let e = a.trim().toLowerCase();
197
+ return v.filter((t) => s !== "all" && t.category !== s ? !1 : e ? `${t.displayName} ${t.description} ${t.packageName}`.toLowerCase().includes(e) : !0);
198
+ }, [
199
+ v,
200
+ a,
201
+ s
202
+ ]);
203
+ return /* @__PURE__ */ f(h, {
204
+ padded: !1,
205
+ children: /* @__PURE__ */ p(g, {
206
+ className: "flex flex-col h-full",
207
+ children: [/* @__PURE__ */ p("div", {
208
+ className: "sticky top-0 z-10 border-b border-border-default bg-bg-surface px-4 sm:px-6 pt-4 pb-3 space-y-3",
209
+ children: [
210
+ /* @__PURE__ */ p("div", {
211
+ className: "flex items-start gap-3",
212
+ children: [/* @__PURE__ */ f(l, { className: "size-6 text-text-primary mt-0.5 flex-shrink-0" }), /* @__PURE__ */ p("div", {
213
+ className: "min-w-0 flex-1",
214
+ children: [/* @__PURE__ */ f("h1", {
215
+ className: "text-xl font-bold text-text-primary",
216
+ children: n("vibecontrols.plugins.title", "Plugins")
217
+ }), /* @__PURE__ */ f("p", {
218
+ className: "text-sm text-text-secondary mt-0.5",
219
+ children: n("vibecontrols.plugins.description", "Browse and install plugins to extend your VibeControls agents.")
220
+ })]
221
+ })]
222
+ }),
223
+ /* @__PURE__ */ p("div", {
224
+ className: "relative",
225
+ children: [
226
+ /* @__PURE__ */ f(u, { className: "absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-secondary" }),
227
+ /* @__PURE__ */ f("input", {
228
+ type: "search",
229
+ value: a,
230
+ onChange: (e) => o(e.target.value),
231
+ placeholder: n("vibecontrols.plugins.searchPlaceholder", "Search by name, description, or package…"),
232
+ "aria-label": n("vibecontrols.plugins.searchLabel", "Search plugins"),
233
+ className: "w-full pl-10 pr-10 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg focus:border-transparent transition-all"
234
+ }),
235
+ a && /* @__PURE__ */ f("button", {
236
+ type: "button",
237
+ onClick: () => o(""),
238
+ "aria-label": n("vibecontrols.plugins.clearSearch", "Clear search"),
239
+ className: "absolute right-3 top-1/2 -translate-y-1/2 p-0.5 rounded hover:bg-bg-elevated transition-colors",
240
+ children: /* @__PURE__ */ f(d, { className: "size-4 text-text-secondary" })
241
+ })
242
+ ]
243
+ }),
244
+ /* @__PURE__ */ f("div", {
245
+ className: "flex flex-wrap gap-2",
246
+ role: "tablist",
247
+ "aria-label": n("vibecontrols.plugins.categories", "Plugin categories"),
248
+ children: _.map((e) => {
249
+ let t = s === e.value;
250
+ return /* @__PURE__ */ f("button", {
251
+ type: "button",
252
+ role: "tab",
253
+ "aria-selected": t,
254
+ onClick: () => m(e.value),
255
+ className: `px-3 py-1 text-xs rounded-full border transition-colors ${t ? "bg-action-primary-bg text-action-primary-text border-action-primary-bg" : "bg-bg-surface text-text-secondary border-border-default hover:bg-bg-sunken hover:text-text-primary"}`,
256
+ children: n(e.labelKey, e.labelFallback)
257
+ }, e.value);
258
+ })
259
+ })
260
+ ]
261
+ }), /* @__PURE__ */ f("div", {
262
+ className: "flex-1 px-4 sm:px-6 py-4",
263
+ children: y.length === 0 ? /* @__PURE__ */ p("div", {
264
+ className: "flex flex-col items-center justify-center text-center py-16 text-text-secondary",
265
+ children: [
266
+ /* @__PURE__ */ f(c, { className: "size-12 mb-3 opacity-50" }),
267
+ /* @__PURE__ */ f("p", {
268
+ className: "text-sm font-medium text-text-primary",
269
+ children: n("vibecontrols.plugins.empty.title", "No matching plugins")
270
+ }),
271
+ /* @__PURE__ */ f("p", {
272
+ className: "text-xs mt-1",
273
+ children: n("vibecontrols.plugins.empty.description", "Try a different search term or category.")
274
+ })
275
+ ]
276
+ }) : /* @__PURE__ */ f("div", {
277
+ className: "grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-3",
278
+ children: y.map((e) => /* @__PURE__ */ f(x, { plugin: e }, e.packageName))
279
+ })
280
+ })]
281
+ })
282
+ });
283
+ }
284
+ //#endregion
285
+ export { S as PluginsCatalogPage };
286
+
287
+ //# sourceMappingURL=PluginsCatalogPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PluginsCatalogPage.js","names":[],"sources":["../../src/pages/PluginsCatalogPage.tsx"],"sourcesContent":["import { useMemo, useState, useCallback } from 'react';\nimport { Check, ChevronDown, Copy, Package, Plug, Search, X } from 'lucide-react';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport { PageContainer, Card } from '@burdenoff/fe-libs/ui';\nimport { PLUGIN_CATALOG, type PluginMeta } from '@/constants/pluginCatalog';\nimport { useTr } from '@/shared/hooks/useTr';\n\ntype PluginType = 'Meta' | 'Provider' | 'Plugin';\n\ninterface CategoryOption {\n value: string;\n labelKey: string;\n labelFallback: string;\n}\n\nconst CATEGORY_OPTIONS: CategoryOption[] = [\n { value: 'all', labelKey: 'vibecontrols.plugins.category.all', labelFallback: 'All' },\n { value: 'ai-core', labelKey: 'vibecontrols.plugins.category.aiCore', labelFallback: 'AI Core' },\n {\n value: 'ai-provider',\n labelKey: 'vibecontrols.plugins.category.aiProvider',\n labelFallback: 'AI Providers',\n },\n {\n value: 'plan-core',\n labelKey: 'vibecontrols.plugins.category.planCore',\n labelFallback: 'Plan Core',\n },\n {\n value: 'plan-provider',\n labelKey: 'vibecontrols.plugins.category.planProvider',\n labelFallback: 'Plan Providers',\n },\n {\n value: 'session-core',\n labelKey: 'vibecontrols.plugins.category.sessionCore',\n labelFallback: 'Session Core',\n },\n {\n value: 'session-provider',\n labelKey: 'vibecontrols.plugins.category.sessionProvider',\n labelFallback: 'Session Providers',\n },\n { value: 'tunnel', labelKey: 'vibecontrols.plugins.category.tunnel', labelFallback: 'Tunnel' },\n { value: 'vcs', labelKey: 'vibecontrols.plugins.category.vcs', labelFallback: 'VCS' },\n {\n value: 'api-playground',\n labelKey: 'vibecontrols.plugins.category.apiPlayground',\n labelFallback: 'API Playground',\n },\n {\n value: 'gitops-core',\n labelKey: 'vibecontrols.plugins.category.gitopsCore',\n labelFallback: 'GitOps Core',\n },\n {\n value: 'gitops-provider',\n labelKey: 'vibecontrols.plugins.category.gitopsProvider',\n labelFallback: 'GitOps Providers',\n },\n {\n value: 'security-core',\n labelKey: 'vibecontrols.plugins.category.securityCore',\n labelFallback: 'Security Core',\n },\n {\n value: 'security-provider',\n labelKey: 'vibecontrols.plugins.category.securityProvider',\n labelFallback: 'Security Providers',\n },\n { value: 'tool', labelKey: 'vibecontrols.plugins.category.tool', labelFallback: 'Tools' },\n];\n\nfunction derivePluginType(category: PluginMeta['category']): PluginType {\n if (category.endsWith('-core')) return 'Meta';\n if (category.endsWith('-provider')) return 'Provider';\n return 'Plugin';\n}\n\nfunction typeBadgeClasses(type: PluginType): string {\n switch (type) {\n case 'Meta':\n return 'bg-accent-purple-subtle text-accent-purple border-accent-purple/30';\n case 'Provider':\n return 'bg-status-info-bg text-status-info-text border-status-info-border';\n default:\n return 'bg-bg-sunken text-text-secondary border-border-default';\n }\n}\n\ninterface CopyButtonProps {\n text: string;\n ariaLabel: string;\n}\n\nfunction CopyButton({ text, ariaLabel }: CopyButtonProps) {\n const [copied, setCopied] = useState(false);\n const handleCopy = useCallback(async () => {\n try {\n if (typeof navigator !== 'undefined' && navigator.clipboard) {\n await navigator.clipboard.writeText(text);\n }\n setCopied(true);\n window.setTimeout(() => setCopied(false), 1500);\n } catch {\n setCopied(false);\n }\n }, [text]);\n\n return (\n <button\n type=\"button\"\n onClick={handleCopy}\n aria-label={ariaLabel}\n className=\"inline-flex items-center justify-center size-7 rounded border border-border-default bg-bg-surface text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors flex-shrink-0\"\n >\n {copied ? <Check className=\"size-3.5\" /> : <Copy className=\"size-3.5\" />}\n </button>\n );\n}\n\ninterface PluginCardProps {\n plugin: PluginMeta;\n}\n\nfunction PluginCard({ plugin }: PluginCardProps) {\n const tr = useTr();\n const type = derivePluginType(plugin.category);\n const installCommand = `npm install ${plugin.packageName}`;\n const usageSnippet = `import plugin from '${plugin.packageName}';`;\n\n const [showUsage, setShowUsage] = useState(false);\n const [showCommands, setShowCommands] = useState(false);\n\n return (\n <Card className=\"flex flex-col h-full p-0 bg-bg-surface border-border-default\">\n <div className=\"flex flex-col gap-3 p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"text-base font-semibold text-text-primary truncate\">\n {plugin.displayName}\n </h3>\n <p className=\"text-xs text-text-secondary font-mono truncate mt-1\">\n {plugin.packageName}\n </p>\n </div>\n <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 text-[10px] uppercase tracking-wide font-semibold rounded-full border ${typeBadgeClasses(type)} flex-shrink-0`}\n >\n {type}\n </span>\n </div>\n\n <p className=\"text-sm text-text-secondary leading-snug\">{plugin.description}</p>\n\n <div className=\"mt-1 space-y-1.5\">\n <p className=\"text-[11px] font-medium uppercase tracking-wide text-text-secondary\">\n {tr('vibecontrols.plugins.install', 'Install')}\n </p>\n <div className=\"flex items-center gap-2 rounded-md border border-border-default bg-bg-sunken px-2.5 py-1.5\">\n <code className=\"flex-1 text-xs font-mono text-text-primary truncate\">\n {installCommand}\n </code>\n <CopyButton\n text={installCommand}\n ariaLabel={tr(\n 'vibecontrols.plugins.copyInstall',\n 'Copy install command for {{name}}',\n {\n name: plugin.displayName,\n }\n )}\n />\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <button\n type=\"button\"\n onClick={() => setShowUsage((value) => !value)}\n className=\"w-full flex items-center justify-between gap-2 text-left text-xs font-medium text-text-secondary hover:text-text-primary transition-colors\"\n aria-expanded={showUsage}\n >\n <span>{tr('vibecontrols.plugins.usage', 'Usage')}</span>\n <ChevronDown\n className={`size-3.5 transition-transform ${showUsage ? 'rotate-180' : ''}`}\n />\n </button>\n {showUsage && (\n <div className=\"flex items-start gap-2 rounded-md border border-border-default bg-bg-sunken px-2.5 py-2\">\n <code className=\"flex-1 text-xs font-mono text-text-primary whitespace-pre-wrap break-all\">\n {usageSnippet}\n </code>\n <CopyButton\n text={usageSnippet}\n ariaLabel={tr('vibecontrols.plugins.copyUsage', 'Copy usage snippet for {{name}}', {\n name: plugin.displayName,\n })}\n />\n </div>\n )}\n </div>\n\n <div className=\"space-y-2\">\n <button\n type=\"button\"\n onClick={() => setShowCommands((value) => !value)}\n className=\"w-full flex items-center justify-between gap-2 text-left text-xs font-medium text-text-secondary hover:text-text-primary transition-colors\"\n aria-expanded={showCommands}\n >\n <span>{tr('vibecontrols.plugins.commands', 'Command reference')}</span>\n <ChevronDown\n className={`size-3.5 transition-transform ${showCommands ? 'rotate-180' : ''}`}\n />\n </button>\n {showCommands && (\n <div className=\"rounded-md border border-border-default bg-bg-sunken px-2.5 py-2\">\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'vibecontrols.plugins.commandsHint',\n 'See the agent admin sub-panel or the plugin README for the documented command list.'\n )}\n </p>\n </div>\n )}\n </div>\n </div>\n </Card>\n );\n}\n\nexport function PluginsCatalogPage() {\n const tr = useTr();\n const [search, setSearch] = useState('');\n const [activeCategory, setActiveCategory] = useState<string>('all');\n\n const pluginList = useMemo(() => {\n return Object.values(PLUGIN_CATALOG).sort((a, b) => a.displayName.localeCompare(b.displayName));\n }, []);\n\n const filteredPlugins = useMemo(() => {\n const term = search.trim().toLowerCase();\n return pluginList.filter((plugin) => {\n if (activeCategory !== 'all' && plugin.category !== activeCategory) return false;\n if (!term) return true;\n const haystack =\n `${plugin.displayName} ${plugin.description} ${plugin.packageName}`.toLowerCase();\n return haystack.includes(term);\n });\n }, [pluginList, search, activeCategory]);\n\n return (\n <PageContainer padded={false}>\n <MotionPage className=\"flex flex-col h-full\">\n {/* Sticky header with search + filter chips */}\n <div className=\"sticky top-0 z-10 border-b border-border-default bg-bg-surface px-4 sm:px-6 pt-4 pb-3 space-y-3\">\n <div className=\"flex items-start gap-3\">\n <Plug className=\"size-6 text-text-primary mt-0.5 flex-shrink-0\" />\n <div className=\"min-w-0 flex-1\">\n <h1 className=\"text-xl font-bold text-text-primary\">\n {tr('vibecontrols.plugins.title', 'Plugins')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n {tr(\n 'vibecontrols.plugins.description',\n 'Browse and install plugins to extend your VibeControls agents.'\n )}\n </p>\n </div>\n </div>\n\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-secondary\" />\n <input\n type=\"search\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n placeholder={tr(\n 'vibecontrols.plugins.searchPlaceholder',\n 'Search by name, description, or package…'\n )}\n aria-label={tr('vibecontrols.plugins.searchLabel', 'Search plugins')}\n className=\"w-full pl-10 pr-10 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg focus:border-transparent transition-all\"\n />\n {search && (\n <button\n type=\"button\"\n onClick={() => setSearch('')}\n aria-label={tr('vibecontrols.plugins.clearSearch', 'Clear search')}\n className=\"absolute right-3 top-1/2 -translate-y-1/2 p-0.5 rounded hover:bg-bg-elevated transition-colors\"\n >\n <X className=\"size-4 text-text-secondary\" />\n </button>\n )}\n </div>\n\n <div\n className=\"flex flex-wrap gap-2\"\n role=\"tablist\"\n aria-label={tr('vibecontrols.plugins.categories', 'Plugin categories')}\n >\n {CATEGORY_OPTIONS.map((option) => {\n const isActive = activeCategory === option.value;\n return (\n <button\n key={option.value}\n type=\"button\"\n role=\"tab\"\n aria-selected={isActive}\n onClick={() => setActiveCategory(option.value)}\n className={`px-3 py-1 text-xs rounded-full border transition-colors ${\n isActive\n ? 'bg-action-primary-bg text-action-primary-text border-action-primary-bg'\n : 'bg-bg-surface text-text-secondary border-border-default hover:bg-bg-sunken hover:text-text-primary'\n }`}\n >\n {tr(option.labelKey, option.labelFallback)}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Grid body */}\n <div className=\"flex-1 px-4 sm:px-6 py-4\">\n {filteredPlugins.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center text-center py-16 text-text-secondary\">\n <Package className=\"size-12 mb-3 opacity-50\" />\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('vibecontrols.plugins.empty.title', 'No matching plugins')}\n </p>\n <p className=\"text-xs mt-1\">\n {tr(\n 'vibecontrols.plugins.empty.description',\n 'Try a different search term or category.'\n )}\n </p>\n </div>\n ) : (\n <div className=\"grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-3\">\n {filteredPlugins.map((plugin) => (\n <PluginCard key={plugin.packageName} plugin={plugin} />\n ))}\n </div>\n )}\n </div>\n </MotionPage>\n </PageContainer>\n );\n}\n"],"mappings":"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"}