@termix-ssh/plugin-sdk 1.0.12 → 1.0.15

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/PLUGIN-GUIDE.md +106 -59
  3. package/dist/backend.d.ts +534 -1
  4. package/dist/backend.js +7 -0
  5. package/dist/capabilities.js +7 -0
  6. package/dist/db.js +1 -0
  7. package/dist/desktop.d.ts +0 -243
  8. package/dist/frontend.d.ts +23 -2
  9. package/dist/host/{PluginsTab-EV4MSGY6.js → PluginsTab-S33WIIGC.js} +511 -178
  10. package/dist/host/{chunk-AWR6FYDO.js → chunk-2IV3VX7F.js} +5552 -5550
  11. package/dist/host/{chunk-RUPLD6RF.js → chunk-3L45NNT5.js} +29 -84
  12. package/dist/host/{chunk-VEWOYAMZ.js → chunk-57DJU2ZA.js} +1 -1
  13. package/dist/host/{chunk-XKA3DBB2.js → chunk-V44FCJBQ.js} +135 -54
  14. package/dist/host/{chunk-X4QZKO5G.js → chunk-VFTB2RDP.js} +54 -5
  15. package/dist/host/core-classes.json +1 -1
  16. package/dist/host/{linked-server-B4NEG4MA.js → linked-server-SCJH7DSL.js} +1 -1
  17. package/dist/host/{rbac-api-OUO3LUOC.js → rbac-api-KA5IPQQL.js} +1 -1
  18. package/dist/host/testing-host.js +4 -4
  19. package/dist/host/types/src/types/index.d.ts +10 -0
  20. package/dist/host/types/src/types/onboarding.d.ts +0 -2
  21. package/dist/host/types/src/types/ui-types.d.ts +3 -1
  22. package/dist/host/types/src/ui/api/audit-log-api.d.ts +0 -10
  23. package/dist/host/types/src/ui/api/auth-methods-api.d.ts +2 -0
  24. package/dist/host/types/src/ui/api/credentials-api.d.ts +0 -22
  25. package/dist/host/types/src/ui/api/plugins-api.d.ts +4 -0
  26. package/dist/host/types/src/ui/api/settings-api.d.ts +6 -6
  27. package/dist/host/types/src/ui/api/ssh-host-management-api.d.ts +6 -28
  28. package/dist/host/types/src/ui/main-axios.d.ts +3 -3
  29. package/dist/host/types/src/ui/onboarding/onboarding-registry.d.ts +3 -1
  30. package/dist/host/types/src/ui/plugin-host/sdk-ui.d.ts +6 -7
  31. package/dist/host/types/src/ui/plugins/plugin-model.d.ts +20 -0
  32. package/dist/host/types/src/ui/plugins/use-plugins-manager.d.ts +3 -0
  33. package/dist/host/types/src/ui/sidebar/AuthEnrollmentSections.d.ts +4 -1
  34. package/dist/host/types/src/ui/sidebar/host-contributions.d.ts +10 -0
  35. package/dist/host/ui.js +36 -210
  36. package/dist/host/{use-permissions-6VSHHUMB.js → use-permissions-LLN6TLAL.js} +1 -1
  37. package/dist/manifest.d.ts +10 -1
  38. package/dist/manifest.js +16 -1
  39. package/dist/ssh-keys.d.ts +29 -0
  40. package/dist/ssh-keys.js +85 -0
  41. package/dist/testing.d.ts +52 -15
  42. package/dist/testing.js +279 -2
  43. package/dist/vitest-preset.js +23 -0
  44. package/package.json +5 -1
  45. package/schema/manifest.schema.json +12 -0
  46. package/dist/host/types/src/ui/sidebar/host-export-payload.d.ts +0 -30
@@ -17,23 +17,27 @@ import {
17
17
  formatCount,
18
18
  groupChanges,
19
19
  isSevere,
20
+ kindsOf,
20
21
  matchesQuery,
21
22
  mergePlugins,
22
23
  mergeReleaseNotes,
23
24
  orderByRisk,
25
+ pageOf,
24
26
  reportIssueUrl,
25
27
  requestFeatureUrl,
26
28
  require_semver,
27
29
  sortPlugins,
28
30
  uploadEntry,
29
31
  youtubeEmbedUrl
30
- } from "./chunk-X4QZKO5G.js";
32
+ } from "./chunk-VFTB2RDP.js";
31
33
  import {
32
34
  ArrowDownWideNarrow,
33
35
  BookOpen,
34
36
  Bug,
35
37
  Button,
36
38
  Check,
39
+ ChevronLeft,
40
+ ChevronRight,
37
41
  Columns3,
38
42
  DEVELOPER_MODE_CHANGED_EVENT,
39
43
  Database,
@@ -49,6 +53,7 @@ import {
49
53
  Input,
50
54
  Library,
51
55
  Lightbulb,
56
+ ListFilter,
52
57
  PANEL,
53
58
  PanePrompt,
54
59
  PanelShell,
@@ -72,6 +77,7 @@ import {
72
77
  TriangleAlert,
73
78
  Upload,
74
79
  ViewToggle,
80
+ X,
75
81
  addRegistry,
76
82
  cn,
77
83
  deletePluginData,
@@ -101,8 +107,8 @@ import {
101
107
  updatePlugin,
102
108
  uploadPlugin,
103
109
  useConfirm
104
- } from "./chunk-AWR6FYDO.js";
105
- import "./chunk-RUPLD6RF.js";
110
+ } from "./chunk-2IV3VX7F.js";
111
+ import "./chunk-3L45NNT5.js";
106
112
  import {
107
113
  __toESM,
108
114
  define_import_meta_env_default,
@@ -111,7 +117,7 @@ import {
111
117
 
112
118
  // src/ui/plugins/PluginsTab.tsx
113
119
  init_define_import_meta_env();
114
- import { useEffect as useEffect5, useMemo as useMemo3, useRef as useRef2, useState as useState5 } from "react";
120
+ import { useEffect as useEffect5, useRef as useRef2, useState as useState5 } from "react";
115
121
  import { useTranslation as useTranslation7 } from "react-i18next";
116
122
 
117
123
  // src/ui/plugins/plugin-bits.tsx
@@ -898,7 +904,16 @@ function PluginDetail({
898
904
  ]
899
905
  }
900
906
  )
901
- ] }) : /* @__PURE__ */ jsxs5(
907
+ ] }) : plugin.unsupported ? /* @__PURE__ */ jsx5(
908
+ "span",
909
+ {
910
+ className: "text-xs text-muted-foreground",
911
+ title: t("plugins.manager.unsupportedHint"),
912
+ children: plugin.needsTermix ? t("plugins.manager.needsTermix", {
913
+ range: plugin.needsTermix
914
+ }) : t("plugins.manager.unsupported")
915
+ }
916
+ ) : /* @__PURE__ */ jsxs5(
902
917
  Button,
903
918
  {
904
919
  variant: "outline",
@@ -1936,7 +1951,7 @@ function PluginsTabBody() {
1936
1951
  onTabChange: (id) => setSection(id)
1937
1952
  }
1938
1953
  ),
1939
- children: /* @__PURE__ */ jsxs6("div", { className: "min-h-0 flex-1 overflow-y-auto thin-scrollbar", children: [
1954
+ children: /* @__PURE__ */ jsxs6("div", { className: "flex min-h-0 flex-1 flex-col", children: [
1940
1955
  manager.managedByServer && /* @__PURE__ */ jsx6(Notice, { children: t("plugins.manager.managedByServer") }),
1941
1956
  manager.registryError && /* @__PURE__ */ jsx6(Notice, { children: t("plugins.manager.registryUnreachable", {
1942
1957
  error: manager.registryError
@@ -1963,14 +1978,14 @@ function PluginsTabBody() {
1963
1978
  layout,
1964
1979
  layoutControls
1965
1980
  }
1966
- ) : section === "updates" ? /* @__PURE__ */ jsx6(
1981
+ ) : /* @__PURE__ */ jsx6("div", { className: "min-h-0 flex-1 overflow-y-auto thin-scrollbar", children: section === "updates" ? /* @__PURE__ */ jsx6(
1967
1982
  UpdatesSection,
1968
1983
  {
1969
1984
  plugins: updates,
1970
1985
  manager,
1971
1986
  onOpen: setDetailId
1972
1987
  }
1973
- ) : /* @__PURE__ */ jsx6(RegistriesSection, { manager })
1988
+ ) : /* @__PURE__ */ jsx6(RegistriesSection, { manager }) })
1974
1989
  ] })
1975
1990
  }
1976
1991
  ),
@@ -2030,7 +2045,7 @@ function Toolbar({
2030
2045
  children
2031
2046
  }) {
2032
2047
  return /* @__PURE__ */ jsxs6("div", { className: "flex flex-wrap items-center gap-2", children: [
2033
- /* @__PURE__ */ jsxs6("div", { className: "relative min-w-40 max-w-xs flex-1", children: [
2048
+ /* @__PURE__ */ jsxs6("div", { className: "relative min-w-40 max-w-md flex-1", children: [
2034
2049
  /* @__PURE__ */ jsx6(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" }),
2035
2050
  /* @__PURE__ */ jsx6(
2036
2051
  Input,
@@ -2148,6 +2163,274 @@ function DescriptionCell({ plugin }) {
2148
2163
  function stop(e) {
2149
2164
  e.stopPropagation();
2150
2165
  }
2166
+ var NO_FACETS = { kind: null, category: null, registry: null };
2167
+ function applyFacets(rows, facets, skip) {
2168
+ return rows.filter(
2169
+ (p) => (skip === "kind" || !facets.kind || p.kinds.includes(facets.kind)) && (skip === "category" || !facets.category || p.category === facets.category) && (skip === "registry" || !facets.registry || p.registryId === facets.registry)
2170
+ );
2171
+ }
2172
+ function activeFacets(facets) {
2173
+ return [facets.kind, facets.category, facets.registry].filter(Boolean).length;
2174
+ }
2175
+ var PAGE_SIZES = ["24", "48", "96"];
2176
+ function usePage(signature) {
2177
+ const [state, setState] = useState5({ signature, page: 1 });
2178
+ const page = state.signature === signature ? state.page : 1;
2179
+ return [page, (next) => setState({ signature, page: next })];
2180
+ }
2181
+ function FacetGroup({
2182
+ title,
2183
+ allLabel,
2184
+ options,
2185
+ value,
2186
+ onChange
2187
+ }) {
2188
+ if (options.length === 0) return null;
2189
+ const row = (key, label, count) => {
2190
+ const active = value === key;
2191
+ return /* @__PURE__ */ jsxs6(
2192
+ "button",
2193
+ {
2194
+ type: "button",
2195
+ "aria-pressed": active,
2196
+ onClick: () => onChange(key),
2197
+ className: cn(
2198
+ "flex w-full items-center gap-2 border-l-2 px-2.5 py-1 text-left text-xs transition-colors",
2199
+ active ? "border-accent-brand bg-accent-brand/10 text-accent-brand" : "border-transparent text-muted-foreground hover:bg-muted/50 hover:text-foreground",
2200
+ count === 0 && !active && "opacity-50"
2201
+ ),
2202
+ children: [
2203
+ /* @__PURE__ */ jsx6("span", { className: "min-w-0 flex-1 truncate", children: label }),
2204
+ count !== void 0 && /* @__PURE__ */ jsx6("span", { className: "shrink-0 text-[10px] tabular-nums opacity-70", children: count })
2205
+ ]
2206
+ },
2207
+ key ?? "__all"
2208
+ );
2209
+ };
2210
+ return /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-0.5", children: [
2211
+ /* @__PURE__ */ jsx6("span", { className: "px-2.5 pb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/70", children: title }),
2212
+ row(null, allLabel),
2213
+ options.map((o) => row(o.value, o.label, o.count))
2214
+ ] });
2215
+ }
2216
+ function FilterRail({
2217
+ rows,
2218
+ facets,
2219
+ onFacets,
2220
+ registries,
2221
+ children
2222
+ }) {
2223
+ const { t } = useTranslation7();
2224
+ const byKind = applyFacets(rows, facets, "kind");
2225
+ const kinds = kindsOf(rows).map(({ kind }) => ({
2226
+ value: kind,
2227
+ label: t(`plugins.manager.kinds.${kind}`),
2228
+ count: byKind.filter((p) => p.kinds.includes(kind)).length
2229
+ }));
2230
+ const byCategory = applyFacets(rows, facets, "category");
2231
+ const categories = categoriesOf(rows).map((c) => ({
2232
+ value: c,
2233
+ label: c,
2234
+ count: byCategory.filter((p) => p.category === c).length
2235
+ }));
2236
+ const byRegistry = applyFacets(rows, facets, "registry");
2237
+ const registryOptions = registries && registries.length > 1 ? registries.map((r) => ({
2238
+ value: r.id,
2239
+ label: r.name,
2240
+ count: byRegistry.filter((p) => p.registryId === r.id).length
2241
+ })) : [];
2242
+ return /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-4 py-3", children: [
2243
+ /* @__PURE__ */ jsx6(
2244
+ FacetGroup,
2245
+ {
2246
+ title: t("plugins.manager.filters.type"),
2247
+ allLabel: t("plugins.manager.filters.allTypes"),
2248
+ options: kinds,
2249
+ value: facets.kind,
2250
+ onChange: (kind) => onFacets({ ...facets, kind })
2251
+ }
2252
+ ),
2253
+ /* @__PURE__ */ jsx6(
2254
+ FacetGroup,
2255
+ {
2256
+ title: t("plugins.manager.filters.category"),
2257
+ allLabel: t("plugins.manager.filters.allCategories"),
2258
+ options: categories,
2259
+ value: facets.category,
2260
+ onChange: (category) => onFacets({ ...facets, category })
2261
+ }
2262
+ ),
2263
+ /* @__PURE__ */ jsx6(
2264
+ FacetGroup,
2265
+ {
2266
+ title: t("plugins.manager.registries.filter"),
2267
+ allLabel: t("plugins.manager.registries.allRegistries"),
2268
+ options: registryOptions,
2269
+ value: facets.registry,
2270
+ onChange: (registry) => onFacets({ ...facets, registry })
2271
+ }
2272
+ ),
2273
+ children && /* @__PURE__ */ jsx6("div", { className: "flex flex-col gap-2.5 border-t border-border px-2.5 pt-3", children })
2274
+ ] });
2275
+ }
2276
+ function RailToggle({
2277
+ label,
2278
+ hint,
2279
+ checked,
2280
+ onChange
2281
+ }) {
2282
+ return /* @__PURE__ */ jsxs6("label", { className: "flex items-center justify-between gap-2", title: hint, children: [
2283
+ /* @__PURE__ */ jsx6("span", { className: "text-xs text-muted-foreground", children: label }),
2284
+ /* @__PURE__ */ jsx6(FakeSwitch, { checked, onChange })
2285
+ ] });
2286
+ }
2287
+ function ActiveFilters({
2288
+ facets,
2289
+ onFacets,
2290
+ registries,
2291
+ shown
2292
+ }) {
2293
+ const { t } = useTranslation7();
2294
+ const chips = [];
2295
+ if (facets.kind) {
2296
+ chips.push({
2297
+ key: "kind",
2298
+ label: t(`plugins.manager.kinds.${facets.kind}`)
2299
+ });
2300
+ }
2301
+ if (facets.category) chips.push({ key: "category", label: facets.category });
2302
+ if (facets.registry) {
2303
+ chips.push({
2304
+ key: "registry",
2305
+ label: registries?.find((r) => r.id === facets.registry)?.name ?? facets.registry
2306
+ });
2307
+ }
2308
+ return /* @__PURE__ */ jsxs6("div", { className: "flex min-h-6 flex-wrap items-center gap-1.5", children: [
2309
+ /* @__PURE__ */ jsx6("span", { className: "text-[11px] text-muted-foreground", children: t("plugins.manager.filters.results", { count: shown }) }),
2310
+ chips.map((chip) => /* @__PURE__ */ jsxs6(
2311
+ "button",
2312
+ {
2313
+ type: "button",
2314
+ onClick: () => onFacets({ ...facets, [chip.key]: null }),
2315
+ className: "flex items-center gap-1 border border-accent-brand/40 bg-accent-brand/10 px-1.5 py-0.5 text-[11px] text-accent-brand hover:bg-accent-brand/20",
2316
+ "aria-label": t("plugins.manager.filters.remove", {
2317
+ name: chip.label
2318
+ }),
2319
+ children: [
2320
+ chip.label,
2321
+ /* @__PURE__ */ jsx6(X, { className: "size-3" })
2322
+ ]
2323
+ },
2324
+ chip.key
2325
+ )),
2326
+ chips.length > 1 && /* @__PURE__ */ jsx6(
2327
+ "button",
2328
+ {
2329
+ type: "button",
2330
+ onClick: () => onFacets(NO_FACETS),
2331
+ className: "text-[11px] text-muted-foreground hover:text-foreground",
2332
+ children: t("plugins.manager.filters.clear")
2333
+ }
2334
+ )
2335
+ ] });
2336
+ }
2337
+ function Pager({
2338
+ page,
2339
+ pages,
2340
+ total,
2341
+ size,
2342
+ onPage,
2343
+ onSize
2344
+ }) {
2345
+ const { t } = useTranslation7();
2346
+ const per = Number(size);
2347
+ if (total <= Number(PAGE_SIZES[0])) return null;
2348
+ return /* @__PURE__ */ jsxs6("div", { className: "flex flex-wrap items-center gap-2 border-t border-border pt-2.5", children: [
2349
+ /* @__PURE__ */ jsx6("span", { className: "text-[11px] tabular-nums text-muted-foreground", children: t("plugins.manager.pager.range", {
2350
+ from: (page - 1) * per + 1,
2351
+ to: Math.min(page * per, total),
2352
+ total
2353
+ }) }),
2354
+ /* @__PURE__ */ jsxs6("div", { className: "ml-auto flex items-center gap-1.5", children: [
2355
+ /* @__PURE__ */ jsxs6(Select, { value: size, onValueChange: (v) => onSize(v), children: [
2356
+ /* @__PURE__ */ jsx6(
2357
+ SelectTrigger,
2358
+ {
2359
+ size: "sm",
2360
+ className: "w-auto text-xs",
2361
+ "aria-label": t("plugins.manager.pager.perPageLabel"),
2362
+ children: /* @__PURE__ */ jsx6(SelectValue, {})
2363
+ }
2364
+ ),
2365
+ /* @__PURE__ */ jsx6(SelectContent, { children: PAGE_SIZES.map((n) => /* @__PURE__ */ jsx6(SelectItem, { value: n, className: "text-xs", children: t("plugins.manager.pager.perPage", { count: Number(n) }) }, n)) })
2366
+ ] }),
2367
+ /* @__PURE__ */ jsx6(
2368
+ Button,
2369
+ {
2370
+ variant: "outline",
2371
+ size: "icon-sm",
2372
+ disabled: page <= 1,
2373
+ onClick: () => onPage(page - 1),
2374
+ title: t("plugins.manager.pager.previous"),
2375
+ "aria-label": t("plugins.manager.pager.previous"),
2376
+ children: /* @__PURE__ */ jsx6(ChevronLeft, { className: "size-3.5" })
2377
+ }
2378
+ ),
2379
+ /* @__PURE__ */ jsx6("span", { className: "px-1 text-[11px] tabular-nums text-muted-foreground", children: t("plugins.manager.pager.page", { page, pages }) }),
2380
+ /* @__PURE__ */ jsx6(
2381
+ Button,
2382
+ {
2383
+ variant: "outline",
2384
+ size: "icon-sm",
2385
+ disabled: page >= pages,
2386
+ onClick: () => onPage(page + 1),
2387
+ title: t("plugins.manager.pager.next"),
2388
+ "aria-label": t("plugins.manager.pager.next"),
2389
+ children: /* @__PURE__ */ jsx6(ChevronRight, { className: "size-3.5" })
2390
+ }
2391
+ )
2392
+ ] })
2393
+ ] });
2394
+ }
2395
+ function FilteredLayout({
2396
+ rail,
2397
+ activeCount,
2398
+ scrollRef,
2399
+ children
2400
+ }) {
2401
+ const { t } = useTranslation7();
2402
+ const [open, setOpen] = useState5(false);
2403
+ const button = /* @__PURE__ */ jsxs6(
2404
+ Button,
2405
+ {
2406
+ variant: "outline",
2407
+ className: cn(
2408
+ "gap-1.5 @3xl:hidden",
2409
+ activeCount > 0 && "border-accent-brand/40 text-accent-brand"
2410
+ ),
2411
+ "aria-expanded": open,
2412
+ onClick: () => setOpen((v) => !v),
2413
+ children: [
2414
+ /* @__PURE__ */ jsx6(ListFilter, { className: "size-3.5" }),
2415
+ activeCount > 0 ? t("plugins.manager.filters.buttonCount", { count: activeCount }) : t("plugins.manager.filters.button")
2416
+ ]
2417
+ }
2418
+ );
2419
+ return /* @__PURE__ */ jsxs6("div", { className: "@container flex min-h-0 flex-1", children: [
2420
+ /* @__PURE__ */ jsx6("aside", { className: "hidden w-52 shrink-0 overflow-y-auto border-r border-border thin-scrollbar @3xl:block", children: rail }),
2421
+ /* @__PURE__ */ jsxs6(
2422
+ "div",
2423
+ {
2424
+ ref: scrollRef,
2425
+ className: "min-h-0 flex-1 overflow-y-auto thin-scrollbar",
2426
+ children: [
2427
+ open && /* @__PURE__ */ jsx6("div", { className: "border-b border-border @3xl:hidden", children: rail }),
2428
+ children(button)
2429
+ ]
2430
+ }
2431
+ )
2432
+ ] });
2433
+ }
2151
2434
  function InstalledSection({
2152
2435
  plugins,
2153
2436
  manager,
@@ -2159,10 +2442,9 @@ function InstalledSection({
2159
2442
  }) {
2160
2443
  const { t } = useTranslation7();
2161
2444
  const [sort, setSort] = useState5("name");
2162
- const rows = sortPlugins(
2163
- plugins.filter((p) => matchesQuery(p, query)),
2164
- sort
2165
- );
2445
+ const [facets, setFacets] = useState5(NO_FACETS);
2446
+ const searched = plugins.filter((p) => matchesQuery(p, query));
2447
+ const rows = sortPlugins(applyFacets(searched, facets), sort);
2166
2448
  const running = rows.filter((p) => p.status === "running");
2167
2449
  const onServer = rows.filter((p) => p.status === "server");
2168
2450
  const stopped = rows.filter(
@@ -2224,66 +2506,84 @@ function InstalledSection({
2224
2506
  onOpen
2225
2507
  }
2226
2508
  );
2227
- return /* @__PURE__ */ jsxs6("div", { className: `flex flex-col ${PANEL.gap} ${PANEL.body}`, children: [
2228
- /* @__PURE__ */ jsx6(
2229
- Toolbar,
2230
- {
2231
- query,
2232
- onQuery,
2233
- placeholder: t("plugins.manager.searchInstalled"),
2234
- children: /* @__PURE__ */ jsxs6("div", { className: "ml-auto flex items-center gap-2", children: [
2235
- /* @__PURE__ */ jsx6(SortSelect, { value: sort, onChange: setSort }),
2236
- layoutControls,
2237
- manager.developerMode && !manager.signedOnly && /* @__PURE__ */ jsx6(UploadButton, { manager }),
2238
- /* @__PURE__ */ jsxs6(
2239
- Button,
2509
+ return /* @__PURE__ */ jsx6(
2510
+ FilteredLayout,
2511
+ {
2512
+ activeCount: activeFacets(facets),
2513
+ rail: /* @__PURE__ */ jsx6(FilterRail, { rows: searched, facets, onFacets: setFacets }),
2514
+ children: (filtersButton) => /* @__PURE__ */ jsxs6("div", { className: `flex flex-col ${PANEL.gap} ${PANEL.body}`, children: [
2515
+ /* @__PURE__ */ jsxs6(
2516
+ Toolbar,
2517
+ {
2518
+ query,
2519
+ onQuery,
2520
+ placeholder: t("plugins.manager.searchInstalled"),
2521
+ children: [
2522
+ filtersButton,
2523
+ /* @__PURE__ */ jsxs6("div", { className: "ml-auto flex items-center gap-2", children: [
2524
+ /* @__PURE__ */ jsx6(SortSelect, { value: sort, onChange: setSort }),
2525
+ layoutControls,
2526
+ manager.developerMode && !manager.signedOnly && /* @__PURE__ */ jsx6(UploadButton, { manager }),
2527
+ /* @__PURE__ */ jsxs6(
2528
+ Button,
2529
+ {
2530
+ variant: "outline",
2531
+ className: "gap-1.5",
2532
+ disabled: pending.length === 0 || manager.busy.size > 0 || manager.managedByServer,
2533
+ onClick: () => void manager.updateAll(),
2534
+ children: [
2535
+ /* @__PURE__ */ jsx6(Download, { className: "size-3.5" }),
2536
+ t("plugins.manager.updateAll")
2537
+ ]
2538
+ }
2539
+ )
2540
+ ] })
2541
+ ]
2542
+ }
2543
+ ),
2544
+ activeFacets(facets) > 0 && /* @__PURE__ */ jsx6(
2545
+ ActiveFilters,
2546
+ {
2547
+ facets,
2548
+ onFacets: setFacets,
2549
+ shown: rows.length
2550
+ }
2551
+ ),
2552
+ rows.length === 0 ? /* @__PURE__ */ jsx6(
2553
+ EmptyState,
2554
+ {
2555
+ icon: Puzzle,
2556
+ title: plugins.length === 0 ? t("plugins.manager.noneInstalled") : t("plugins.manager.nothingMatches")
2557
+ }
2558
+ ) : /* @__PURE__ */ jsxs6(Fragment4, { children: [
2559
+ running.length > 0 && /* @__PURE__ */ jsx6(
2560
+ Group,
2240
2561
  {
2241
- variant: "outline",
2242
- className: "gap-1.5",
2243
- disabled: pending.length === 0 || manager.busy.size > 0 || manager.managedByServer,
2244
- onClick: () => void manager.updateAll(),
2245
- children: [
2246
- /* @__PURE__ */ jsx6(Download, { className: "size-3.5" }),
2247
- t("plugins.manager.updateAll")
2248
- ]
2562
+ title: t("plugins.manager.groups.running"),
2563
+ count: running.length,
2564
+ children: collection(running)
2565
+ }
2566
+ ),
2567
+ onServer.length > 0 && /* @__PURE__ */ jsx6(
2568
+ Group,
2569
+ {
2570
+ title: t("plugins.manager.groups.onServer"),
2571
+ count: onServer.length,
2572
+ children: collection(onServer)
2573
+ }
2574
+ ),
2575
+ stopped.length > 0 && /* @__PURE__ */ jsx6(
2576
+ Group,
2577
+ {
2578
+ title: t("plugins.manager.groups.notRunning"),
2579
+ count: stopped.length,
2580
+ children: collection(stopped)
2249
2581
  }
2250
2582
  )
2251
2583
  ] })
2252
- }
2253
- ),
2254
- rows.length === 0 ? /* @__PURE__ */ jsx6(
2255
- EmptyState,
2256
- {
2257
- icon: Puzzle,
2258
- title: plugins.length === 0 ? t("plugins.manager.noneInstalled") : t("plugins.manager.nothingMatches")
2259
- }
2260
- ) : /* @__PURE__ */ jsxs6(Fragment4, { children: [
2261
- running.length > 0 && /* @__PURE__ */ jsx6(
2262
- Group,
2263
- {
2264
- title: t("plugins.manager.groups.running"),
2265
- count: running.length,
2266
- children: collection(running)
2267
- }
2268
- ),
2269
- onServer.length > 0 && /* @__PURE__ */ jsx6(
2270
- Group,
2271
- {
2272
- title: t("plugins.manager.groups.onServer"),
2273
- count: onServer.length,
2274
- children: collection(onServer)
2275
- }
2276
- ),
2277
- stopped.length > 0 && /* @__PURE__ */ jsx6(
2278
- Group,
2279
- {
2280
- title: t("plugins.manager.groups.notRunning"),
2281
- count: stopped.length,
2282
- children: collection(stopped)
2283
- }
2284
- )
2285
- ] })
2286
- ] });
2584
+ ] })
2585
+ }
2586
+ );
2287
2587
  }
2288
2588
  function UploadButton({ manager }) {
2289
2589
  const { t } = useTranslation7();
@@ -2570,31 +2870,49 @@ function BrowseSection({
2570
2870
  layoutControls
2571
2871
  }) {
2572
2872
  const { t } = useTranslation7();
2573
- const [category, setCategory] = useState5(null);
2574
- const [registry, setRegistry] = useState5(null);
2873
+ const [facets, setFacets] = useState5(NO_FACETS);
2575
2874
  const [sort, setSort] = useState5("popular");
2576
2875
  const [installedChoice, setInstalledChoice] = useStoredChoice(
2577
2876
  "pluginsTab.browseInstalled",
2578
2877
  ["hide", "show"],
2579
2878
  "hide"
2580
2879
  );
2880
+ const [unsupportedChoice, setUnsupportedChoice] = useStoredChoice(
2881
+ "pluginsTab.browseUnsupported",
2882
+ ["hide", "show"],
2883
+ "hide"
2884
+ );
2885
+ const [pageSize, setPageSize] = useStoredChoice(
2886
+ "pluginsTab.pageSize",
2887
+ PAGE_SIZES,
2888
+ "24"
2889
+ );
2890
+ const scroller = useRef2(null);
2581
2891
  const showInstalled = installedChoice === "show";
2582
- const categories = useMemo3(() => categoriesOf(plugins), [plugins]);
2892
+ const showUnsupported = unsupportedChoice === "show";
2583
2893
  const registries = manager.registries;
2584
- const registryFilter = registries.some((entry) => entry.id === registry) ? registry : null;
2585
- const rows = sortPlugins(
2586
- plugins.filter(
2587
- (p) => (showInstalled || !p.installed) && matchesQuery(p, query) && (category === null || p.category === category) && (registryFilter === null || p.registryId === registryFilter)
2588
- ),
2589
- sort
2894
+ const unsupportedCount = plugins.filter(
2895
+ (p) => p.unsupported && !p.installed
2896
+ ).length;
2897
+ const searched = plugins.filter(
2898
+ (p) => (showInstalled || !p.installed) && (showUnsupported || !p.unsupported || p.installed) && matchesQuery(p, query)
2590
2899
  );
2900
+ const rows = sortPlugins(applyFacets(searched, facets), sort);
2901
+ const [page, setPage] = usePage(
2902
+ JSON.stringify([query, facets, sort, showInstalled, showUnsupported])
2903
+ );
2904
+ const paged = pageOf(rows, page, Number(pageSize));
2905
+ const goTo = (next) => {
2906
+ setPage(next);
2907
+ if (scroller.current) scroller.current.scrollTop = 0;
2908
+ };
2591
2909
  const allInstalledHidden = !showInstalled && plugins.length > 0 && plugins.every((p) => p.installed);
2592
2910
  const listColumns = [
2593
2911
  {
2594
2912
  key: "name",
2595
2913
  header: t("plugins.manager.table.name"),
2596
2914
  width: "minmax(0,1.3fr)",
2597
- cell: (p) => /* @__PURE__ */ jsx6(NameCell, { plugin: p })
2915
+ cell: (p) => /* @__PURE__ */ jsx6(NameCell, { plugin: p, muted: p.unsupported })
2598
2916
  },
2599
2917
  {
2600
2918
  key: "description",
@@ -2626,117 +2944,124 @@ function BrowseSection({
2626
2944
  {
2627
2945
  key: "actions",
2628
2946
  header: "",
2629
- width: "120px",
2947
+ width: "150px",
2630
2948
  align: "end",
2631
2949
  cell: (p) => /* @__PURE__ */ jsx6(BrowseAction, { plugin: p, manager })
2632
2950
  }
2633
2951
  ];
2634
- return /* @__PURE__ */ jsxs6("div", { className: `flex flex-col ${PANEL.gap} ${PANEL.body}`, children: [
2635
- /* @__PURE__ */ jsx6(
2636
- Toolbar,
2637
- {
2638
- query,
2639
- onQuery,
2640
- placeholder: t("plugins.manager.searchPlugins"),
2641
- children: /* @__PURE__ */ jsxs6("div", { className: "ml-auto flex items-center gap-2", children: [
2642
- /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-1.5", children: [
2643
- /* @__PURE__ */ jsx6("span", { className: "text-xs text-muted-foreground", children: t("plugins.manager.showInstalled") }),
2952
+ return /* @__PURE__ */ jsx6(
2953
+ FilteredLayout,
2954
+ {
2955
+ activeCount: activeFacets(facets),
2956
+ scrollRef: scroller,
2957
+ rail: /* @__PURE__ */ jsxs6(
2958
+ FilterRail,
2959
+ {
2960
+ rows: searched,
2961
+ facets,
2962
+ onFacets: setFacets,
2963
+ registries,
2964
+ children: [
2644
2965
  /* @__PURE__ */ jsx6(
2645
- FakeSwitch,
2966
+ RailToggle,
2646
2967
  {
2968
+ label: t("plugins.manager.showInstalled"),
2647
2969
  checked: showInstalled,
2648
2970
  onChange: (v) => setInstalledChoice(v ? "show" : "hide")
2649
2971
  }
2972
+ ),
2973
+ unsupportedCount > 0 && /* @__PURE__ */ jsx6(
2974
+ RailToggle,
2975
+ {
2976
+ label: t("plugins.manager.showUnsupported", {
2977
+ count: unsupportedCount
2978
+ }),
2979
+ hint: t("plugins.manager.showUnsupportedHint"),
2980
+ checked: showUnsupported,
2981
+ onChange: (v) => setUnsupportedChoice(v ? "show" : "hide")
2982
+ }
2650
2983
  )
2651
- ] }),
2652
- registries.length > 1 && /* @__PURE__ */ jsxs6(
2653
- Select,
2654
- {
2655
- value: registryFilter ?? "__all",
2656
- onValueChange: (v) => setRegistry(v === "__all" ? null : v),
2657
- children: [
2658
- /* @__PURE__ */ jsxs6(
2659
- SelectTrigger,
2660
- {
2661
- size: "sm",
2662
- className: "w-auto gap-1.5 text-xs",
2663
- "aria-label": t("plugins.manager.registries.filter"),
2664
- children: [
2665
- /* @__PURE__ */ jsx6(Library, { className: "size-3.5 text-muted-foreground" }),
2666
- /* @__PURE__ */ jsx6(SelectValue, {})
2667
- ]
2668
- }
2669
- ),
2670
- /* @__PURE__ */ jsxs6(SelectContent, { children: [
2671
- /* @__PURE__ */ jsx6(SelectItem, { value: "__all", className: "text-xs", children: t("plugins.manager.registries.allRegistries") }),
2672
- registries.map((entry) => /* @__PURE__ */ jsx6(
2673
- SelectItem,
2674
- {
2675
- value: entry.id,
2676
- className: "text-xs",
2677
- children: entry.name
2678
- },
2679
- entry.id
2680
- ))
2681
- ] })
2682
- ]
2683
- }
2684
- ),
2685
- /* @__PURE__ */ jsx6(SortSelect, { value: sort, onChange: setSort }),
2686
- layoutControls
2687
- ] })
2688
- }
2689
- ),
2690
- categories.length > 0 && /* @__PURE__ */ jsx6("div", { className: "flex flex-wrap items-center gap-1", children: [null, ...categories].map((c) => /* @__PURE__ */ jsx6(
2691
- "button",
2692
- {
2693
- type: "button",
2694
- onClick: () => setCategory(c),
2695
- className: cn(
2696
- "border px-2 py-1 text-[11px] font-medium transition-colors",
2697
- category === c ? "border-accent-brand bg-accent-brand/10 text-accent-brand" : "border-border text-muted-foreground hover:text-foreground"
2984
+ ]
2985
+ }
2986
+ ),
2987
+ children: (filtersButton) => /* @__PURE__ */ jsxs6("div", { className: `flex flex-col ${PANEL.gap} ${PANEL.body}`, children: [
2988
+ /* @__PURE__ */ jsxs6(
2989
+ Toolbar,
2990
+ {
2991
+ query,
2992
+ onQuery,
2993
+ placeholder: t("plugins.manager.searchPlugins"),
2994
+ children: [
2995
+ filtersButton,
2996
+ /* @__PURE__ */ jsxs6("div", { className: "ml-auto flex items-center gap-2", children: [
2997
+ /* @__PURE__ */ jsx6(SortSelect, { value: sort, onChange: setSort }),
2998
+ layoutControls
2999
+ ] })
3000
+ ]
3001
+ }
2698
3002
  ),
2699
- children: c ?? t("plugins.manager.allCategories")
2700
- },
2701
- c ?? "__all"
2702
- )) }),
2703
- rows.length === 0 ? allInstalledHidden ? /* @__PURE__ */ jsx6(
2704
- EmptyState,
2705
- {
2706
- icon: Puzzle,
2707
- title: t("plugins.manager.allInstalled"),
2708
- hint: t("plugins.manager.allInstalledHint")
2709
- }
2710
- ) : /* @__PURE__ */ jsx6(
2711
- EmptyState,
2712
- {
2713
- icon: Puzzle,
2714
- title: t("plugins.manager.nothingHere"),
2715
- hint: plugins.length === 0 ? void 0 : t("plugins.manager.nothingHereHint")
2716
- }
2717
- ) : /* @__PURE__ */ jsx6(
2718
- PluginCollection,
2719
- {
2720
- plugins: rows,
2721
- layout,
2722
- renderCard: (p) => /* @__PURE__ */ jsx6(
2723
- BrowseCard,
3003
+ /* @__PURE__ */ jsx6(
3004
+ ActiveFilters,
2724
3005
  {
2725
- plugin: p,
2726
- manager,
2727
- onOpen: () => onOpen(p.id)
3006
+ facets,
3007
+ onFacets: setFacets,
3008
+ registries,
3009
+ shown: rows.length
2728
3010
  }
2729
3011
  ),
2730
- listColumns,
2731
- onOpen
2732
- }
2733
- ),
2734
- /* @__PURE__ */ jsxs6("p", { className: "text-[11px] text-muted-foreground", children: [
2735
- t("plugins.manager.registries.browseHint"),
2736
- " ",
2737
- /* @__PURE__ */ jsx6(DocsLink, { core: "communityRegistry", className: "text-[11px]", children: t("plugins.manager.communitySubmit") })
2738
- ] })
2739
- ] });
3012
+ rows.length === 0 ? allInstalledHidden ? /* @__PURE__ */ jsx6(
3013
+ EmptyState,
3014
+ {
3015
+ icon: Puzzle,
3016
+ title: t("plugins.manager.allInstalled"),
3017
+ hint: t("plugins.manager.allInstalledHint")
3018
+ }
3019
+ ) : /* @__PURE__ */ jsx6(
3020
+ EmptyState,
3021
+ {
3022
+ icon: Puzzle,
3023
+ title: t("plugins.manager.nothingHere"),
3024
+ hint: plugins.length === 0 ? void 0 : t("plugins.manager.nothingHereHint")
3025
+ }
3026
+ ) : /* @__PURE__ */ jsx6(
3027
+ PluginCollection,
3028
+ {
3029
+ plugins: paged.items,
3030
+ layout,
3031
+ renderCard: (p) => /* @__PURE__ */ jsx6(
3032
+ BrowseCard,
3033
+ {
3034
+ plugin: p,
3035
+ manager,
3036
+ onOpen: () => onOpen(p.id)
3037
+ }
3038
+ ),
3039
+ listColumns,
3040
+ onOpen
3041
+ }
3042
+ ),
3043
+ /* @__PURE__ */ jsx6(
3044
+ Pager,
3045
+ {
3046
+ page: paged.page,
3047
+ pages: paged.pages,
3048
+ total: rows.length,
3049
+ size: pageSize,
3050
+ onPage: goTo,
3051
+ onSize: (next) => {
3052
+ setPageSize(next);
3053
+ goTo(1);
3054
+ }
3055
+ }
3056
+ ),
3057
+ /* @__PURE__ */ jsxs6("p", { className: "text-[11px] text-muted-foreground", children: [
3058
+ t("plugins.manager.registries.browseHint"),
3059
+ " ",
3060
+ /* @__PURE__ */ jsx6(DocsLink, { core: "communityRegistry", className: "text-[11px]", children: t("plugins.manager.communitySubmit") })
3061
+ ] })
3062
+ ] })
3063
+ }
3064
+ );
2740
3065
  }
2741
3066
  function BrowseCard({
2742
3067
  plugin,
@@ -2750,6 +3075,7 @@ function BrowseCard({
2750
3075
  CardHead,
2751
3076
  {
2752
3077
  plugin,
3078
+ muted: plugin.unsupported,
2753
3079
  onOpen,
2754
3080
  facts: /* @__PURE__ */ jsxs6(Fragment4, { children: [
2755
3081
  /* @__PURE__ */ jsx6(
@@ -2770,7 +3096,7 @@ function BrowseCard({
2770
3096
  }
2771
3097
  ),
2772
3098
  /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 border-t border-border px-3 py-2", children: [
2773
- /* @__PURE__ */ jsx6("span", { className: "min-w-0 flex-1 truncate text-[10px] text-muted-foreground", children: severe.length > 0 ? t(`plugins.capabilities.${severe[0]}.title`, {
3099
+ /* @__PURE__ */ jsx6("span", { className: "min-w-0 flex-1 truncate text-[10px] text-muted-foreground", children: plugin.unsupported && !plugin.installed ? "" : severe.length > 0 ? t(`plugins.capabilities.${severe[0]}.title`, {
2774
3100
  defaultValue: severe[0],
2775
3101
  nsSeparator: false
2776
3102
  }) : t("plugins.manager.noServerAccess") }),
@@ -2794,7 +3120,14 @@ function BrowseAction({
2794
3120
  plugin.installed ? /* @__PURE__ */ jsxs6("span", { className: "flex shrink-0 items-center gap-1 text-[11px] font-medium text-accent-brand", children: [
2795
3121
  /* @__PURE__ */ jsx6(Check, { className: "size-3" }),
2796
3122
  t("plugins.manager.installedBadge")
2797
- ] }) : /* @__PURE__ */ jsx6(
3123
+ ] }) : plugin.unsupported ? /* @__PURE__ */ jsx6(
3124
+ "span",
3125
+ {
3126
+ className: "shrink-0 text-[11px] text-muted-foreground",
3127
+ title: t("plugins.manager.unsupportedHint"),
3128
+ children: plugin.needsTermix ? t("plugins.manager.needsTermix", { range: plugin.needsTermix }) : t("plugins.manager.unsupported")
3129
+ }
3130
+ ) : /* @__PURE__ */ jsx6(
2798
3131
  Button,
2799
3132
  {
2800
3133
  variant: "outline",