@dxos/plugin-registry 0.8.4-main.c85a9c8dae → 0.8.4-main.d9fc60f731

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 (265) hide show
  1. package/LICENSE +102 -5
  2. package/dist/lib/neutral/RegistryPlugin.mjs +27 -0
  3. package/dist/lib/neutral/RegistryPlugin.mjs.map +7 -0
  4. package/dist/lib/{browser/cli/index.mjs → neutral/RegistryPlugin.node.mjs} +28 -99
  5. package/dist/lib/neutral/RegistryPlugin.node.mjs.map +7 -0
  6. package/dist/lib/neutral/RegistryPlugin.workerd.mjs +10 -0
  7. package/dist/lib/neutral/RegistryPlugin.workerd.mjs.map +7 -0
  8. package/dist/lib/neutral/chunk-GRQI6MLI.mjs +36 -0
  9. package/dist/lib/neutral/chunk-GRQI6MLI.mjs.map +7 -0
  10. package/dist/lib/neutral/chunk-XK2JMH3H.mjs +33 -0
  11. package/dist/lib/neutral/chunk-XK2JMH3H.mjs.map +7 -0
  12. package/dist/lib/neutral/index.mjs +25 -0
  13. package/dist/lib/neutral/index.mjs.map +7 -0
  14. package/dist/lib/neutral/meta.json +1 -0
  15. package/dist/lib/neutral/open-plugin-registry-II735N76.mjs +15 -0
  16. package/dist/lib/neutral/open-plugin-registry-II735N76.mjs.map +7 -0
  17. package/dist/lib/neutral/plugin.mjs +18 -0
  18. package/dist/lib/neutral/plugin.mjs.map +7 -0
  19. package/dist/lib/neutral/translations.mjs +72 -0
  20. package/dist/lib/neutral/translations.mjs.map +7 -0
  21. package/dist/types/src/RegistryPlugin.d.ts +1 -0
  22. package/dist/types/src/RegistryPlugin.d.ts.map +1 -1
  23. package/dist/types/src/{cli/plugin.d.ts → RegistryPlugin.node.d.ts} +2 -1
  24. package/dist/types/src/RegistryPlugin.node.d.ts.map +1 -0
  25. package/dist/types/src/RegistryPlugin.test.d.ts +2 -0
  26. package/dist/types/src/RegistryPlugin.test.d.ts.map +1 -0
  27. package/dist/types/src/RegistryPlugin.workerd.d.ts +4 -0
  28. package/dist/types/src/RegistryPlugin.workerd.d.ts.map +1 -0
  29. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  30. package/dist/types/src/capabilities/dev-plugin-loader.d.ts +11 -0
  31. package/dist/types/src/capabilities/dev-plugin-loader.d.ts.map +1 -0
  32. package/dist/types/src/capabilities/index.d.ts +15 -3
  33. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  34. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  35. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  36. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  37. package/dist/types/src/capabilities/settings.d.ts +14 -0
  38. package/dist/types/src/capabilities/settings.d.ts.map +1 -0
  39. package/dist/types/src/categories.d.ts +18 -0
  40. package/dist/types/src/categories.d.ts.map +1 -0
  41. package/dist/types/src/{cli/commands → commands}/disable.d.ts +2 -2
  42. package/dist/types/src/commands/disable.d.ts.map +1 -0
  43. package/dist/types/src/{cli/commands → commands}/enable.d.ts +2 -2
  44. package/dist/types/src/commands/enable.d.ts.map +1 -0
  45. package/dist/types/src/commands/index.d.ts.map +1 -0
  46. package/dist/types/src/commands/list.d.ts +7 -0
  47. package/dist/types/src/commands/list.d.ts.map +1 -0
  48. package/dist/types/src/commands/util.d.ts.map +1 -0
  49. package/dist/types/src/components/DisableDependentsAlert/DisableDependentsAlert.d.ts +24 -0
  50. package/dist/types/src/components/DisableDependentsAlert/DisableDependentsAlert.d.ts.map +1 -0
  51. package/dist/types/src/components/DisableDependentsAlert/index.d.ts +2 -0
  52. package/dist/types/src/components/DisableDependentsAlert/index.d.ts.map +1 -0
  53. package/dist/types/src/components/PluginDetail/PluginDetail.d.ts +75 -2
  54. package/dist/types/src/components/PluginDetail/PluginDetail.d.ts.map +1 -1
  55. package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts +62 -16
  56. package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts.map +1 -1
  57. package/dist/types/src/components/PluginFailureBadge/PluginFailureBadge.d.ts +20 -0
  58. package/dist/types/src/components/PluginFailureBadge/PluginFailureBadge.d.ts.map +1 -0
  59. package/dist/types/src/components/PluginFailureBadge/index.d.ts +2 -0
  60. package/dist/types/src/components/PluginFailureBadge/index.d.ts.map +1 -0
  61. package/dist/types/src/components/PluginList/PluginItem.d.ts +29 -2
  62. package/dist/types/src/components/PluginList/PluginItem.d.ts.map +1 -1
  63. package/dist/types/src/components/PluginList/PluginList.d.ts +18 -3
  64. package/dist/types/src/components/PluginList/PluginList.d.ts.map +1 -1
  65. package/dist/types/src/components/PluginList/PluginList.stories.d.ts +60 -16
  66. package/dist/types/src/components/PluginList/PluginList.stories.d.ts.map +1 -1
  67. package/dist/types/src/components/RegistrySettings/RegistrySettings.d.ts +18 -0
  68. package/dist/types/src/components/RegistrySettings/RegistrySettings.d.ts.map +1 -0
  69. package/dist/types/src/components/RegistrySettings/index.d.ts +2 -0
  70. package/dist/types/src/components/RegistrySettings/index.d.ts.map +1 -0
  71. package/dist/types/src/components/index.d.ts +3 -0
  72. package/dist/types/src/components/index.d.ts.map +1 -1
  73. package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts +20 -0
  74. package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts.map +1 -0
  75. package/dist/types/src/containers/BaseRegistryArticle/index.d.ts +2 -0
  76. package/dist/types/src/containers/BaseRegistryArticle/index.d.ts.map +1 -0
  77. package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts +5 -0
  78. package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts.map +1 -0
  79. package/dist/types/src/containers/LoadPluginDialog/index.d.ts +2 -0
  80. package/dist/types/src/containers/LoadPluginDialog/index.d.ts.map +1 -0
  81. package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts.map +1 -1
  82. package/dist/types/src/containers/PluginArticle/index.d.ts +1 -2
  83. package/dist/types/src/containers/PluginArticle/index.d.ts.map +1 -1
  84. package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts +8 -0
  85. package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts.map +1 -0
  86. package/dist/types/src/containers/PublicRegistryArticle/index.d.ts +2 -0
  87. package/dist/types/src/containers/PublicRegistryArticle/index.d.ts.map +1 -0
  88. package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts +14 -0
  89. package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts.map +1 -0
  90. package/dist/types/src/containers/RegistryArticle/index.d.ts +2 -0
  91. package/dist/types/src/containers/RegistryArticle/index.d.ts.map +1 -0
  92. package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts +13 -0
  93. package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts.map +1 -0
  94. package/dist/types/src/containers/RegistrySettingsContainer/index.d.ts +2 -0
  95. package/dist/types/src/containers/RegistrySettingsContainer/index.d.ts.map +1 -0
  96. package/dist/types/src/containers/index.d.ts +5 -1
  97. package/dist/types/src/containers/index.d.ts.map +1 -1
  98. package/dist/types/src/hooks/index.d.ts +9 -0
  99. package/dist/types/src/hooks/index.d.ts.map +1 -0
  100. package/dist/types/src/hooks/useAutoTags.d.ts +20 -0
  101. package/dist/types/src/hooks/useAutoTags.d.ts.map +1 -0
  102. package/dist/types/src/hooks/useCatalogEntry.d.ts +11 -0
  103. package/dist/types/src/hooks/useCatalogEntry.d.ts.map +1 -0
  104. package/dist/types/src/hooks/useDisableConfirmation.d.ts +19 -0
  105. package/dist/types/src/hooks/useDisableConfirmation.d.ts.map +1 -0
  106. package/dist/types/src/hooks/useInstalledVersionTag.d.ts +18 -0
  107. package/dist/types/src/hooks/useInstalledVersionTag.d.ts.map +1 -0
  108. package/dist/types/src/hooks/usePluginActions.d.ts +36 -0
  109. package/dist/types/src/hooks/usePluginActions.d.ts.map +1 -0
  110. package/dist/types/src/hooks/useRegistryPlugins.d.ts +15 -0
  111. package/dist/types/src/hooks/useRegistryPlugins.d.ts.map +1 -0
  112. package/dist/types/src/hooks/useUpdateAvailable.d.ts +18 -0
  113. package/dist/types/src/hooks/useUpdateAvailable.d.ts.map +1 -0
  114. package/dist/types/src/hooks/useVersionPicker.d.ts +21 -0
  115. package/dist/types/src/hooks/useVersionPicker.d.ts.map +1 -0
  116. package/dist/types/src/index.d.ts +1 -1
  117. package/dist/types/src/index.d.ts.map +1 -1
  118. package/dist/types/src/meta.d.ts +20 -2
  119. package/dist/types/src/meta.d.ts.map +1 -1
  120. package/dist/types/src/operations/index.d.ts +3 -0
  121. package/dist/types/src/operations/index.d.ts.map +1 -0
  122. package/dist/types/src/operations/open-plugin-registry.d.ts +5 -0
  123. package/dist/types/src/operations/open-plugin-registry.d.ts.map +1 -0
  124. package/dist/types/src/plugin.d.ts +4 -0
  125. package/dist/types/src/plugin.d.ts.map +1 -0
  126. package/dist/types/src/{cli/storage.d.ts → storage.d.ts} +4 -4
  127. package/dist/types/src/storage.d.ts.map +1 -0
  128. package/dist/types/src/translations.d.ts +59 -16
  129. package/dist/types/src/translations.d.ts.map +1 -1
  130. package/dist/types/src/types.d.ts +30 -1
  131. package/dist/types/src/types.d.ts.map +1 -1
  132. package/dist/types/tsconfig.tsbuildinfo +1 -1
  133. package/package.json +63 -39
  134. package/src/{cli/plugin.ts → RegistryPlugin.node.ts} +3 -1
  135. package/src/RegistryPlugin.test.ts +24 -0
  136. package/src/RegistryPlugin.tsx +13 -5
  137. package/src/RegistryPlugin.workerd.ts +11 -0
  138. package/src/capabilities/app-graph-builder.ts +219 -0
  139. package/src/capabilities/dev-plugin-loader.ts +39 -0
  140. package/src/capabilities/index.ts +11 -3
  141. package/src/capabilities/operation-handler.ts +16 -0
  142. package/src/capabilities/react-surface.tsx +126 -0
  143. package/src/capabilities/settings.ts +34 -0
  144. package/src/categories.ts +44 -0
  145. package/src/components/DisableDependentsAlert/DisableDependentsAlert.tsx +69 -0
  146. package/src/components/DisableDependentsAlert/index.ts +5 -0
  147. package/src/components/PluginDetail/PluginDetail.stories.tsx +18 -14
  148. package/src/components/PluginDetail/PluginDetail.tsx +323 -61
  149. package/src/components/PluginFailureBadge/PluginFailureBadge.tsx +68 -0
  150. package/src/components/PluginFailureBadge/index.ts +5 -0
  151. package/src/components/PluginList/PluginItem.tsx +124 -25
  152. package/src/components/PluginList/PluginList.stories.tsx +22 -16
  153. package/src/components/PluginList/PluginList.tsx +32 -4
  154. package/src/components/RegistrySettings/RegistrySettings.tsx +120 -0
  155. package/src/components/RegistrySettings/index.ts +5 -0
  156. package/src/components/index.ts +3 -0
  157. package/src/containers/BaseRegistryArticle/BaseRegistryArticle.tsx +178 -0
  158. package/src/containers/BaseRegistryArticle/index.ts +5 -0
  159. package/src/containers/LoadPluginDialog/LoadPluginDialog.tsx +88 -0
  160. package/src/containers/LoadPluginDialog/index.ts +5 -0
  161. package/src/containers/PluginArticle/PluginArticle.tsx +102 -13
  162. package/src/containers/PluginArticle/index.ts +1 -3
  163. package/src/containers/PublicRegistryArticle/PublicRegistryArticle.tsx +193 -0
  164. package/src/containers/PublicRegistryArticle/index.ts +5 -0
  165. package/src/containers/RegistryArticle/RegistryArticle.tsx +53 -0
  166. package/src/containers/RegistryArticle/index.ts +5 -0
  167. package/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.tsx +59 -0
  168. package/src/containers/RegistrySettingsContainer/index.ts +5 -0
  169. package/src/containers/index.ts +5 -1
  170. package/src/hooks/index.ts +12 -0
  171. package/src/hooks/useAutoTags.ts +52 -0
  172. package/src/hooks/useCatalogEntry.ts +25 -0
  173. package/src/hooks/useDisableConfirmation.ts +58 -0
  174. package/src/hooks/useInstalledVersionTag.ts +41 -0
  175. package/src/hooks/usePluginActions.ts +174 -0
  176. package/src/hooks/useRegistryPlugins.ts +29 -0
  177. package/src/hooks/useUpdateAvailable.ts +31 -0
  178. package/src/hooks/useVersionPicker.ts +112 -0
  179. package/src/index.ts +1 -1
  180. package/src/meta.ts +34 -8
  181. package/src/operations/index.ts +7 -0
  182. package/src/operations/open-plugin-registry.ts +21 -0
  183. package/src/plugin.ts +11 -0
  184. package/src/{cli/storage.ts → storage.ts} +14 -21
  185. package/src/translations.ts +64 -15
  186. package/src/types.ts +33 -1
  187. package/dist/lib/browser/PluginArticle-MLBF7AFA.mjs +0 -29
  188. package/dist/lib/browser/PluginArticle-MLBF7AFA.mjs.map +0 -7
  189. package/dist/lib/browser/PluginRegistry-ENAI4SAY.mjs +0 -77
  190. package/dist/lib/browser/PluginRegistry-ENAI4SAY.mjs.map +0 -7
  191. package/dist/lib/browser/app-graph-builder-2HXD7SOS.mjs +0 -167
  192. package/dist/lib/browser/app-graph-builder-2HXD7SOS.mjs.map +0 -7
  193. package/dist/lib/browser/chunk-TSNK3SAR.mjs +0 -191
  194. package/dist/lib/browser/chunk-TSNK3SAR.mjs.map +0 -7
  195. package/dist/lib/browser/chunk-VOAOTNGS.mjs +0 -19
  196. package/dist/lib/browser/chunk-VOAOTNGS.mjs.map +0 -7
  197. package/dist/lib/browser/cli/index.mjs.map +0 -7
  198. package/dist/lib/browser/index.mjs +0 -63
  199. package/dist/lib/browser/index.mjs.map +0 -7
  200. package/dist/lib/browser/meta.json +0 -1
  201. package/dist/lib/browser/operation-resolver-7P3NNVSR.mjs +0 -25
  202. package/dist/lib/browser/operation-resolver-7P3NNVSR.mjs.map +0 -7
  203. package/dist/lib/browser/react-surface-DH25DIRH.mjs +0 -85
  204. package/dist/lib/browser/react-surface-DH25DIRH.mjs.map +0 -7
  205. package/dist/lib/node-esm/PluginArticle-TP2G6GGI.mjs +0 -30
  206. package/dist/lib/node-esm/PluginArticle-TP2G6GGI.mjs.map +0 -7
  207. package/dist/lib/node-esm/PluginRegistry-CWIZIQNX.mjs +0 -78
  208. package/dist/lib/node-esm/PluginRegistry-CWIZIQNX.mjs.map +0 -7
  209. package/dist/lib/node-esm/app-graph-builder-2LNPN72O.mjs +0 -168
  210. package/dist/lib/node-esm/app-graph-builder-2LNPN72O.mjs.map +0 -7
  211. package/dist/lib/node-esm/chunk-4XO63XB6.mjs +0 -192
  212. package/dist/lib/node-esm/chunk-4XO63XB6.mjs.map +0 -7
  213. package/dist/lib/node-esm/chunk-SALMIZ2W.mjs +0 -21
  214. package/dist/lib/node-esm/chunk-SALMIZ2W.mjs.map +0 -7
  215. package/dist/lib/node-esm/cli/index.mjs +0 -225
  216. package/dist/lib/node-esm/cli/index.mjs.map +0 -7
  217. package/dist/lib/node-esm/index.mjs +0 -64
  218. package/dist/lib/node-esm/index.mjs.map +0 -7
  219. package/dist/lib/node-esm/meta.json +0 -1
  220. package/dist/lib/node-esm/operation-resolver-B24QW6IF.mjs +0 -26
  221. package/dist/lib/node-esm/operation-resolver-B24QW6IF.mjs.map +0 -7
  222. package/dist/lib/node-esm/react-surface-3YBEVL65.mjs +0 -86
  223. package/dist/lib/node-esm/react-surface-3YBEVL65.mjs.map +0 -7
  224. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts.map +0 -1
  225. package/dist/types/src/capabilities/app-graph-builder/index.d.ts +0 -3
  226. package/dist/types/src/capabilities/app-graph-builder/index.d.ts.map +0 -1
  227. package/dist/types/src/capabilities/operation-resolver/index.d.ts +0 -3
  228. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +0 -1
  229. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +0 -5
  230. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +0 -1
  231. package/dist/types/src/capabilities/react-surface/index.d.ts +0 -3
  232. package/dist/types/src/capabilities/react-surface/index.d.ts.map +0 -1
  233. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +0 -1
  234. package/dist/types/src/cli/commands/disable.d.ts.map +0 -1
  235. package/dist/types/src/cli/commands/enable.d.ts.map +0 -1
  236. package/dist/types/src/cli/commands/index.d.ts.map +0 -1
  237. package/dist/types/src/cli/commands/list.d.ts +0 -7
  238. package/dist/types/src/cli/commands/list.d.ts.map +0 -1
  239. package/dist/types/src/cli/commands/util.d.ts.map +0 -1
  240. package/dist/types/src/cli/index.d.ts +0 -3
  241. package/dist/types/src/cli/index.d.ts.map +0 -1
  242. package/dist/types/src/cli/plugin.d.ts.map +0 -1
  243. package/dist/types/src/cli/storage.d.ts.map +0 -1
  244. package/dist/types/src/containers/PluginRegistry/PluginRegistry.d.ts +0 -8
  245. package/dist/types/src/containers/PluginRegistry/PluginRegistry.d.ts.map +0 -1
  246. package/dist/types/src/containers/PluginRegistry/index.d.ts +0 -3
  247. package/dist/types/src/containers/PluginRegistry/index.d.ts.map +0 -1
  248. package/src/capabilities/app-graph-builder/app-graph-builder.ts +0 -137
  249. package/src/capabilities/app-graph-builder/index.ts +0 -7
  250. package/src/capabilities/operation-resolver/index.ts +0 -7
  251. package/src/capabilities/operation-resolver/operation-resolver.ts +0 -24
  252. package/src/capabilities/react-surface/index.ts +0 -7
  253. package/src/capabilities/react-surface/react-surface.tsx +0 -88
  254. package/src/cli/index.ts +0 -6
  255. package/src/containers/PluginRegistry/PluginRegistry.tsx +0 -86
  256. package/src/containers/PluginRegistry/index.ts +0 -7
  257. /package/dist/types/src/capabilities/{app-graph-builder/app-graph-builder.d.ts → app-graph-builder.d.ts} +0 -0
  258. /package/dist/types/src/capabilities/{react-surface/react-surface.d.ts → react-surface.d.ts} +0 -0
  259. /package/dist/types/src/{cli/commands → commands}/index.d.ts +0 -0
  260. /package/dist/types/src/{cli/commands → commands}/util.d.ts +0 -0
  261. /package/src/{cli/commands → commands}/disable.ts +0 -0
  262. /package/src/{cli/commands → commands}/enable.ts +0 -0
  263. /package/src/{cli/commands → commands}/index.ts +0 -0
  264. /package/src/{cli/commands → commands}/list.ts +0 -0
  265. /package/src/{cli/commands → commands}/util.ts +0 -0
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './PluginFailureBadge';
@@ -2,37 +2,100 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import React, { type MouseEvent, useCallback } from 'react';
5
+ import React, { type MouseEvent, useCallback, useMemo } from 'react';
6
6
 
7
- import { type Plugin } from '@dxos/app-framework';
8
- import { type ChromaticPalette, Icon, IconButton, Input, Link, ListItem, Tag, useTranslation } from '@dxos/react-ui';
7
+ import { type Plugin, type PluginManager } from '@dxos/app-framework';
8
+ import {
9
+ Button,
10
+ type ChromaticPalette,
11
+ Icon,
12
+ IconButton,
13
+ Input,
14
+ Link,
15
+ ListItem,
16
+ type NeutralPalette,
17
+ Tag,
18
+ useTranslation,
19
+ } from '@dxos/react-ui';
9
20
  import { mx } from '@dxos/ui-theme';
10
21
  import { getStyles } from '@dxos/ui-theme';
11
22
 
12
- import { meta } from '../../meta';
13
- import { type RegistryTagType } from '../../types';
23
+ import { meta } from '#meta';
24
+ import { type RegistryTagType } from '#types';
25
+
26
+ import { PluginFailureBadge } from '../PluginFailureBadge';
14
27
 
15
28
  export type PluginItemProps = {
16
29
  plugin: Plugin.Plugin;
30
+ /** Ids of plugins currently installed (loaded into the manager). */
17
31
  installed?: readonly string[];
32
+ /** Ids of plugins whose install is in flight. */
33
+ installing?: readonly string[];
18
34
  enabled?: readonly string[];
35
+ /**
36
+ * Derived tags (e.g. `registry`, `local`) to display alongside the plugin's own meta.tags.
37
+ * Not persisted to plugin meta; computed per-render by the container.
38
+ */
39
+ extraTags?: readonly string[];
19
40
  onClick?: (id: string) => void;
20
41
  onChange?: (id: string, enabled: boolean) => void;
42
+ /**
43
+ * Install handler. When provided and the plugin is not installed, an Install button
44
+ * is rendered in place of the enable switch.
45
+ */
46
+ onInstall?: (id: string) => void;
47
+ /**
48
+ * When true and plugin is installed, shows an Update button instead of the enable switch.
49
+ */
50
+ hasUpdate?: boolean;
51
+ /** Called when the user clicks the Update button. */
52
+ onUpdate?: (id: string) => void;
53
+ /** Ids of plugins whose update is currently in flight. */
54
+ updating?: readonly string[];
21
55
  hasSettings?: (id: string) => boolean;
22
56
  onSettings?: (id: string) => void;
57
+ /**
58
+ * Failure record for this plugin, if any. When present a warning badge is
59
+ * rendered next to the plugin name; clicking it opens a popover with the
60
+ * phase, reason, and error message.
61
+ */
62
+ failure?: PluginManager.PluginFailure;
23
63
  };
24
64
 
25
65
  export const PluginItem = ({
26
66
  plugin,
67
+ installed,
68
+ installing,
27
69
  enabled = [],
70
+ extraTags,
28
71
  onClick,
29
72
  onChange,
73
+ onInstall,
74
+ hasUpdate,
75
+ onUpdate,
76
+ updating,
30
77
  hasSettings: hasSettingsProp,
31
78
  onSettings,
79
+ failure,
32
80
  }: PluginItemProps) => {
33
81
  const { t } = useTranslation(meta.id);
34
82
  const { id, name, description, tags, icon = 'ph--circle--regular', iconHue = 'neutral' } = plugin.meta;
83
+ const displayTags = useMemo(() => {
84
+ if (!extraTags || extraTags.length === 0) {
85
+ return tags ?? [];
86
+ }
87
+ const set = new Set<string>(tags ?? []);
88
+ for (const tag of extraTags) {
89
+ set.add(tag);
90
+ }
91
+ return Array.from(set);
92
+ }, [tags, extraTags]);
35
93
  const isEnabled = enabled.includes(id);
94
+ const isInstalled = installed ? installed.includes(id) : true;
95
+ const isInstalling = installing?.includes(id) ?? false;
96
+ const isUpdating = updating?.includes(id) ?? false;
97
+ const showInstallButton = !!onInstall && !isInstalled;
98
+ const showUpdateButton = !!onUpdate && isInstalled && !!hasUpdate;
36
99
  const inputId = `${id}-input`;
37
100
  const labelId = `${id}-label`;
38
101
  const descriptionId = `${id}-description`;
@@ -46,11 +109,27 @@ export const PluginItem = ({
46
109
  [id, isEnabled, onChange],
47
110
  );
48
111
 
112
+ const handleInstall = useCallback(
113
+ (event: MouseEvent) => {
114
+ event.stopPropagation();
115
+ onInstall?.(id);
116
+ },
117
+ [id, onInstall],
118
+ );
119
+
120
+ const handleUpdate = useCallback(
121
+ (event: MouseEvent) => {
122
+ event.stopPropagation();
123
+ onUpdate?.(id);
124
+ },
125
+ [id, onUpdate],
126
+ );
127
+
49
128
  const hasSettings = hasSettingsProp?.(id) ?? false;
50
129
  const handleSettings = useCallback(() => onSettings?.(id), [id, onSettings]);
51
130
  const styles = getStyles(iconHue);
52
131
  const gridCols = 'grid grid-cols-[5rem_1fr]';
53
- const gridRows = 'grid grid-rows-[40px_1fr_min-content_40px]';
132
+ const gridRows = 'grid grid-cols-1 grid-rows-[40px_1fr_min-content_40px]';
54
133
 
55
134
  return (
56
135
  <ListItem.Root
@@ -58,29 +137,27 @@ export const PluginItem = ({
58
137
  labelId={labelId}
59
138
  data-testid={`pluginList.${id}`}
60
139
  aria-describedby={descriptionId}
61
- classNames={mx(gridCols, 'h-[12rem] w-full gap-3 pe-2 bg-modal-surface rounded-md overflow-hidden')}
140
+ classNames={mx(gridCols, 'h-[14rem] w-full gap-3 pe-2 bg-modal-surface rounded-md overflow-hidden')}
62
141
  >
63
- <div className={mx(gridRows, 'justify-center rounded-l-md', styles.surface)}>
64
- <Icon
65
- classNames={mx('row-start-2 cursor-pointer', styles.surfaceText)}
66
- icon={icon}
67
- size={14}
68
- onClick={handleClick}
69
- />
142
+ <div className={mx(gridRows, 'rounded-l-md', styles.surface)}>
143
+ <div className='flex justify-center row-start-2 cursor-pointer' onClick={handleClick}>
144
+ <Icon classNames={styles.fg} icon={icon} size={14} />
145
+ </div>
70
146
  </div>
71
147
 
72
- <div className={mx(gridRows)}>
73
- <div className='flex items-center overflow-hidden cursor-pointer' onClick={handleClick}>
148
+ <div className={mx(gridRows, 'min-w-0')}>
149
+ <div className='flex items-center gap-2 overflow-hidden cursor-pointer' onClick={handleClick}>
74
150
  <span className='text-lg truncate'>{name ?? id}</span>
151
+ {failure && <PluginFailureBadge failure={failure} />}
75
152
  </div>
76
153
 
77
154
  <div>
78
- <p className={mx('text-description', 'line-clamp-4 min-w-0')}>{description}</p>
155
+ <p className='text-description line-clamp-4 min-w-0'>{description}</p>
79
156
  </div>
80
157
 
81
158
  <div className='flex -ms-0.5 overflow-x-auto scrollbar-none'>
82
- {tags?.map((tag) => (
83
- <Tag key={tag} palette={tagColors[tag as RegistryTagType]} classNames='text-xs uppercase font-thin'>
159
+ {displayTags.map((tag) => (
160
+ <Tag key={tag} palette={tagColors[tag as RegistryTagType]} classNames='text-xs uppercase'>
84
161
  {tag}
85
162
  </Tag>
86
163
  ))}
@@ -91,7 +168,7 @@ export const PluginItem = ({
91
168
  aria-describedby={descriptionId}
92
169
  classNames='cursor-pointer'
93
170
  icon='ph--gear--regular'
94
- label={t('settings label')}
171
+ label={t('plugin-settings.label')}
95
172
  iconOnly
96
173
  size={4}
97
174
  onClick={handleSettings}
@@ -99,14 +176,34 @@ export const PluginItem = ({
99
176
  />
100
177
 
101
178
  <Link aria-describedby={descriptionId} classNames='text-description cursor-pointer' onClick={handleClick}>
102
- {t('details label')}
179
+ {t('details.label')}
103
180
  </Link>
104
181
 
105
182
  <div className='grow' />
106
183
  <div className='pe-1'>
107
- <Input.Root id={inputId}>
108
- <Input.Switch classNames='self-center' checked={isEnabled} onClick={handleChange} />
109
- </Input.Root>
184
+ {isUpdating ? (
185
+ <Button aria-describedby={descriptionId} density='md' variant='primary' disabled>
186
+ {t('updating.label')}
187
+ </Button>
188
+ ) : showUpdateButton ? (
189
+ <Button aria-describedby={descriptionId} density='md' variant='primary' onClick={handleUpdate}>
190
+ {t('update.label')}
191
+ </Button>
192
+ ) : showInstallButton ? (
193
+ <Button
194
+ aria-describedby={descriptionId}
195
+ density='md'
196
+ variant='primary'
197
+ disabled={isInstalling}
198
+ onClick={handleInstall}
199
+ >
200
+ {isInstalling ? t('installing.label') : t('install.label')}
201
+ </Button>
202
+ ) : (
203
+ <Input.Root id={inputId}>
204
+ <Input.Switch classNames='self-center' checked={isEnabled} onClick={handleChange} />
205
+ </Input.Root>
206
+ )}
110
207
  </div>
111
208
  </div>
112
209
  </div>
@@ -114,11 +211,13 @@ export const PluginItem = ({
114
211
  );
115
212
  };
116
213
 
117
- const tagColors: Record<RegistryTagType, ChromaticPalette> = {
214
+ const tagColors: Record<RegistryTagType, ChromaticPalette | NeutralPalette> = {
118
215
  new: 'rose',
119
216
  beta: 'teal',
120
217
  labs: 'blue',
121
218
  popular: 'green',
122
219
  featured: 'pink',
123
220
  experimental: 'amber',
221
+ registry: 'indigo',
222
+ local: 'neutral',
124
223
  };
@@ -6,17 +6,18 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import React, { useState } from 'react';
7
7
 
8
8
  import { type Plugin, Plugin as PluginNS } from '@dxos/app-framework';
9
- import { faker } from '@dxos/random';
9
+ import { DXN } from '@dxos/keys';
10
+ import { random } from '@dxos/random';
10
11
  import { ScrollArea } from '@dxos/react-ui';
11
12
  import { withLayout, withTheme } from '@dxos/react-ui/testing';
12
13
  import { getHashHue } from '@dxos/ui-theme';
13
14
 
14
- import { translations } from '../../translations';
15
- import { RegistryTagType } from '../../types';
15
+ import { translations } from '#translations';
16
+ import { RegistryTagType } from '#types';
16
17
 
17
18
  import { PluginList } from './PluginList';
18
19
 
19
- faker.seed(1);
20
+ random.seed(1);
20
21
 
21
22
  const icons = [
22
23
  'ph--bug--regular',
@@ -29,18 +30,20 @@ const icons = [
29
30
 
30
31
  const DefaultStory = () => {
31
32
  const [plugins] = useState<Plugin.Plugin[]>(
32
- faker.helpers.multiple(
33
+ random.helpers.multiple(
33
34
  () =>
34
- PluginNS.define({
35
- id: `dxos.org/plugin/plugin-${faker.string.uuid()}`,
36
- name: `${faker.commerce.productName()}`,
37
- description: faker.lorem.sentences(Math.ceil(Math.random() * 3)),
38
- tags: faker.helpers.uniqueArray(RegistryTagType.literals as any, Math.floor(Math.random() * 3)),
39
- icon: faker.helpers.arrayElement(icons),
40
- iconHue: getHashHue(faker.string.uuid()),
41
- homePage: faker.datatype.boolean({ probability: 0.5 }) ? faker.internet.url() : undefined,
42
- source: faker.internet.url(),
43
- }).pipe(PluginNS.make)(),
35
+ PluginNS.define(
36
+ PluginNS.makeMeta({
37
+ key: DXN.make('org.dxos.plugin.test'),
38
+ name: `${random.commerce.productName()}`,
39
+ description: random.lorem.sentences(Math.ceil(Math.random() * 3)),
40
+ tags: random.helpers.uniqueArray(RegistryTagType.literals as any, Math.floor(Math.random() * 3)),
41
+ icon: random.helpers.arrayElement(icons),
42
+ iconHue: getHashHue(random.string.uuid()),
43
+ homePage: random.datatype.boolean({ probability: 0.5 }) ? random.internet.url() : undefined,
44
+ source: random.internet.url(),
45
+ }),
46
+ ).pipe(PluginNS.make)(),
44
47
  { count: 32 },
45
48
  ),
46
49
  );
@@ -73,7 +76,10 @@ export default meta;
73
76
  type Story = StoryObj<typeof meta>;
74
77
 
75
78
  export const Default: Story = {
76
- decorators: [withTheme(), withLayout({ layout: 'column' })],
79
+ decorators: [withTheme(), withLayout({ layout: 'column', classNames: 'bg-deck-surface' })],
80
+ parameters: {
81
+ layout: 'fullscreen',
82
+ },
77
83
  };
78
84
 
79
85
  export const FullScreen: Story = {
@@ -4,20 +4,48 @@
4
4
 
5
5
  import React from 'react';
6
6
 
7
- import { type Plugin } from '@dxos/app-framework';
7
+ import { type Plugin, type PluginManager } from '@dxos/app-framework';
8
8
  import { List } from '@dxos/react-ui';
9
9
 
10
10
  import { PluginItem, type PluginItemProps } from './PluginItem';
11
11
 
12
- export type PluginListProps = Omit<PluginItemProps, 'plugin'> & {
12
+ export type PluginListProps = Omit<PluginItemProps, 'plugin' | 'extraTags' | 'hasUpdate' | 'failure'> & {
13
13
  plugins?: readonly Plugin.Plugin[];
14
+ /**
15
+ * Map from plugin id → extra tags to display (e.g. `registry`, `local`).
16
+ * Computed by the container; not persisted to plugin meta.
17
+ */
18
+ extraTagsById?: Record<string, readonly string[]>;
19
+ /**
20
+ * Set of plugin ids for which a newer version is available in the catalog.
21
+ * Causes each matching item to render an Update button instead of the enable switch.
22
+ */
23
+ updateAvailableIds?: ReadonlySet<string>;
24
+ /**
25
+ * Map from plugin id → most recent failure record. Used to render a warning
26
+ * badge next to the plugin name. Sourced from `PluginManager.failed`.
27
+ */
28
+ failuresById?: Record<string, PluginManager.PluginFailure>;
14
29
  };
15
30
 
16
- export const PluginList = ({ plugins = [], ...props }: PluginListProps) => {
31
+ export const PluginList = ({
32
+ plugins = [],
33
+ extraTagsById,
34
+ updateAvailableIds,
35
+ failuresById,
36
+ ...props
37
+ }: PluginListProps) => {
17
38
  return (
18
39
  <List classNames='grid grid-cols-[repeat(auto-fill,minmax(18rem,1fr))] auto-rows-[max-content] gap-4 p-4'>
19
40
  {plugins.map((plugin) => (
20
- <PluginItem key={plugin.meta.id} plugin={plugin} {...props} />
41
+ <PluginItem
42
+ key={plugin.meta.id}
43
+ plugin={plugin}
44
+ extraTags={extraTagsById?.[plugin.meta.id]}
45
+ hasUpdate={updateAvailableIds?.has(plugin.meta.id)}
46
+ failure={failuresById?.[plugin.meta.id]}
47
+ {...props}
48
+ />
21
49
  ))}
22
50
  </List>
23
51
  );
@@ -0,0 +1,120 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import React, { useCallback, useState } from 'react';
6
+
7
+ import { type AppSurface } from '@dxos/app-toolkit/ui';
8
+ import { log } from '@dxos/log';
9
+ import { Button, Input, Message, useTranslation } from '@dxos/react-ui';
10
+ import { Settings as SettingsForm } from '@dxos/react-ui-form';
11
+
12
+ import { meta } from '#meta';
13
+
14
+ import { type RegistrySettings as RegistrySettingsType } from '../../types';
15
+
16
+ export type RegistrySettingsProps = AppSurface.SettingsArticleProps<
17
+ RegistrySettingsType,
18
+ {
19
+ activeDevPluginIds: readonly string[];
20
+ onEnableDev: (url: string) => Promise<void>;
21
+ onDisableDev: (id: string) => Promise<void>;
22
+ }
23
+ >;
24
+
25
+ /**
26
+ * Settings panel for `@dxos/plugin-registry`. The dev-plugin section lets a
27
+ * plugin author point Composer at a local Vite dev server (defaults to
28
+ * `localhost:3967`) and toggle the dev plugin on/off persistently — boot will
29
+ * re-attach it on the next reload, surviving HMR-triggered reloads. If the
30
+ * dev server is offline at boot, the toggle stays on and a warning is logged
31
+ * (the manager's `failed` atom also surfaces a badge on the plugin list).
32
+ */
33
+ export const RegistrySettings = ({
34
+ settings,
35
+ onSettingsChange,
36
+ activeDevPluginIds,
37
+ onEnableDev,
38
+ onDisableDev,
39
+ }: RegistrySettingsProps) => {
40
+ const { t } = useTranslation(meta.id);
41
+ const [busy, setBusy] = useState(false);
42
+ const enabled = !!settings.devPluginEnabled;
43
+ const url = settings.devPluginUrl ?? '';
44
+ const trimmedUrl = url.trim();
45
+ const loadedDevId = activeDevPluginIds[0];
46
+
47
+ const handleToggle = useCallback(async () => {
48
+ if (busy || !onSettingsChange) {
49
+ return;
50
+ }
51
+ setBusy(true);
52
+ try {
53
+ if (enabled && loadedDevId) {
54
+ await onDisableDev(loadedDevId);
55
+ onSettingsChange((current) => ({ ...current, devPluginEnabled: false }));
56
+ } else if (enabled) {
57
+ // Toggle is on but no dev plugin is currently loaded (boot-time load
58
+ // failed or hasn't finished). Treat the click as "turn it off."
59
+ onSettingsChange((current) => ({ ...current, devPluginEnabled: false }));
60
+ } else {
61
+ if (!trimmedUrl) {
62
+ return;
63
+ }
64
+ // Optimistically persist intent so the next reload retries even if
65
+ // the page closes mid-load.
66
+ onSettingsChange((current) => ({ ...current, devPluginEnabled: true }));
67
+ try {
68
+ await onEnableDev(trimmedUrl);
69
+ } catch (error) {
70
+ // Per product direction: leave the intent on, log the failure. The
71
+ // user explicitly turns it off if they're done iterating.
72
+ log.warn('dev plugin enable failed', { url: trimmedUrl, error });
73
+ }
74
+ }
75
+ } finally {
76
+ setBusy(false);
77
+ }
78
+ }, [busy, enabled, loadedDevId, onSettingsChange, onDisableDev, onEnableDev, trimmedUrl]);
79
+
80
+ // Button label tracks the persisted intent, not the current load state, so
81
+ // a transient "intent on but not loaded" combination still reads as
82
+ // "Disable Dev Plugin" — clicking it turns the intent off cleanly.
83
+ const buttonLabel = busy
84
+ ? t('dev-plugin.busy.label')
85
+ : enabled
86
+ ? t('dev-plugin.disable.label')
87
+ : t('dev-plugin.enable.label');
88
+
89
+ return (
90
+ <SettingsForm.Viewport>
91
+ <SettingsForm.Section title={t('dev-plugin.section.title')}>
92
+ <Message.Root valence='neutral'>
93
+ <Message.Content>{t('dev-plugin.description')}</Message.Content>
94
+ </Message.Root>
95
+ <SettingsForm.Item title={t('dev-plugin.url.label')} description={t('dev-plugin.url.description')}>
96
+ <Input.TextInput
97
+ classNames='ms-2'
98
+ disabled={!onSettingsChange || enabled || busy}
99
+ value={url}
100
+ onChange={(event) => onSettingsChange?.((current) => ({ ...current, devPluginUrl: event.target.value }))}
101
+ />
102
+ </SettingsForm.Item>
103
+ <SettingsForm.Item title={t('dev-plugin.toggle.label')} description={t('dev-plugin.toggle.description')}>
104
+ <Button
105
+ variant={enabled ? undefined : 'primary'}
106
+ disabled={busy || (!enabled && !trimmedUrl)}
107
+ onClick={() => void handleToggle()}
108
+ >
109
+ {buttonLabel}
110
+ </Button>
111
+ </SettingsForm.Item>
112
+ {enabled && !loadedDevId && !busy && (
113
+ <Message.Root valence='warning'>
114
+ <Message.Content>{t('dev-plugin.not-loaded.message')}</Message.Content>
115
+ </Message.Root>
116
+ )}
117
+ </SettingsForm.Section>
118
+ </SettingsForm.Viewport>
119
+ );
120
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './RegistrySettings';
@@ -2,5 +2,8 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ export * from './DisableDependentsAlert';
5
6
  export * from './PluginDetail';
7
+ export * from './PluginFailureBadge';
6
8
  export * from './PluginList';
9
+ export * from './RegistrySettings';
@@ -0,0 +1,178 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { useAtomValue } from '@effect-atom/atom-react';
6
+ import * as Effect from 'effect/Effect';
7
+ import React, { type ReactNode, useCallback, useMemo, useState } from 'react';
8
+
9
+ import { type Plugin } from '@dxos/app-framework';
10
+ import { useCapabilities, useOperationInvoker, usePluginManager } from '@dxos/app-framework/ui';
11
+ import { AppCapabilities, LayoutOperation, SettingsOperation } from '@dxos/app-toolkit';
12
+ import { runAndForwardErrors } from '@dxos/effect';
13
+ import { ObservabilityOperation } from '@dxos/plugin-observability';
14
+ import { Input, Panel, ScrollArea, Toolbar, useTranslation } from '@dxos/react-ui';
15
+ import { composable, composableProps } from '@dxos/react-ui';
16
+
17
+ import { PluginList, type PluginListProps } from '#components';
18
+ import { getPluginPath, meta } from '#meta';
19
+
20
+ import { useDisableConfirmation } from '../../hooks';
21
+
22
+ const matchesFilter = (plugin: Plugin.Plugin, query: string) => {
23
+ const haystack = `${plugin.meta.name ?? ''} ${plugin.meta.id}`.toLowerCase();
24
+ return haystack.includes(query);
25
+ };
26
+
27
+ export type BaseRegistryArticleProps = {
28
+ /** Article id used as the pivotId when opening a plugin's detail surface. */
29
+ id: string;
30
+ /** Plugins to display, pre-sorted by the caller. */
31
+ plugins: readonly Plugin.Plugin[];
32
+ /**
33
+ * Distinguishes the source of toggle events for observability
34
+ * (e.g. `'registry'` for the public registry surface).
35
+ */
36
+ source?: string;
37
+ /** Rendered in place of the list when no plugins match the current filter. */
38
+ empty?: ReactNode;
39
+ } & Pick<
40
+ PluginListProps,
41
+ | 'installed'
42
+ | 'installing'
43
+ | 'updating'
44
+ | 'updateAvailableIds'
45
+ | 'extraTagsById'
46
+ | 'failuresById'
47
+ | 'onInstall'
48
+ | 'onUpdate'
49
+ >;
50
+
51
+ export const BaseRegistryArticle = composable<HTMLDivElement, BaseRegistryArticleProps>(
52
+ (
53
+ {
54
+ id,
55
+ plugins,
56
+ source,
57
+ empty,
58
+ installed,
59
+ installing,
60
+ updating,
61
+ updateAvailableIds,
62
+ extraTagsById,
63
+ failuresById,
64
+ onInstall,
65
+ onUpdate,
66
+ ...props
67
+ },
68
+ forwardedRef,
69
+ ) => {
70
+ const { t } = useTranslation(meta.id);
71
+ const manager = usePluginManager();
72
+ const { invoke, invokePromise } = useOperationInvoker();
73
+ const allSettings = useCapabilities(AppCapabilities.Settings);
74
+ const enabled = useAtomValue(manager.enabled);
75
+ const [filter, setFilter] = useState('');
76
+
77
+ const filtered = useMemo(() => {
78
+ const query = filter.trim().toLowerCase();
79
+ return query.length === 0 ? plugins : plugins.filter((plugin) => matchesFilter(plugin, query));
80
+ }, [plugins, filter]);
81
+
82
+ const dispatchToggle = useCallback(
83
+ (pluginId: string, nextEnabled: boolean) =>
84
+ Effect.gen(function* () {
85
+ if (nextEnabled) {
86
+ yield* manager.enable(pluginId);
87
+ } else {
88
+ yield* manager.disable(pluginId);
89
+ }
90
+ yield* invoke(ObservabilityOperation.SendEvent, {
91
+ name: 'plugins.toggle',
92
+ properties: source
93
+ ? { plugin: pluginId, enabled: nextEnabled, source }
94
+ : { plugin: pluginId, enabled: nextEnabled },
95
+ });
96
+ }).pipe(runAndForwardErrors),
97
+ [invoke, manager, source],
98
+ );
99
+
100
+ const requestDisable = useDisableConfirmation(manager, (id) => void dispatchToggle(id, false));
101
+
102
+ const handleChange = useCallback(
103
+ (pluginId: string, nextEnabled: boolean) => {
104
+ if (nextEnabled) {
105
+ void dispatchToggle(pluginId, true);
106
+ return;
107
+ }
108
+ requestDisable(pluginId);
109
+ },
110
+ [dispatchToggle, requestDisable],
111
+ );
112
+
113
+ const handleClick = useCallback(
114
+ (pluginId: string) =>
115
+ invokePromise(LayoutOperation.Open, {
116
+ subject: [getPluginPath(pluginId)],
117
+ pivotId: id,
118
+ positioning: 'end',
119
+ }),
120
+ [invokePromise, id],
121
+ );
122
+
123
+ const hasSettings = useCallback(
124
+ (pluginId: string) => allSettings.some((setting) => setting.prefix === pluginId),
125
+ [allSettings],
126
+ );
127
+
128
+ const handleSettings = useCallback(
129
+ (pluginId: string) => invokePromise(SettingsOperation.Open, { plugin: pluginId }),
130
+ [invokePromise],
131
+ );
132
+
133
+ return (
134
+ <Panel.Root {...composableProps(props)} ref={forwardedRef}>
135
+ <Panel.Toolbar asChild>
136
+ <Toolbar.Root>
137
+ <Input.Root>
138
+ <Input.Label srOnly>{t('filter.label')}</Input.Label>
139
+ <Input.TextInput
140
+ placeholder={t('filter.placeholder')}
141
+ value={filter}
142
+ onChange={(event) => setFilter(event.target.value)}
143
+ />
144
+ </Input.Root>
145
+ </Toolbar.Root>
146
+ </Panel.Toolbar>
147
+ <Panel.Content asChild>
148
+ <ScrollArea.Root orientation='vertical'>
149
+ <ScrollArea.Viewport>
150
+ {filtered.length > 0 ? (
151
+ <PluginList
152
+ plugins={filtered}
153
+ enabled={enabled}
154
+ installed={installed}
155
+ installing={installing}
156
+ updating={updating}
157
+ updateAvailableIds={updateAvailableIds}
158
+ extraTagsById={extraTagsById}
159
+ failuresById={failuresById}
160
+ onClick={handleClick}
161
+ onChange={handleChange}
162
+ onInstall={onInstall}
163
+ onUpdate={onUpdate}
164
+ hasSettings={hasSettings}
165
+ onSettings={handleSettings}
166
+ />
167
+ ) : (
168
+ empty
169
+ )}
170
+ </ScrollArea.Viewport>
171
+ </ScrollArea.Root>
172
+ </Panel.Content>
173
+ </Panel.Root>
174
+ );
175
+ },
176
+ );
177
+
178
+ BaseRegistryArticle.displayName = 'BaseRegistryArticle';
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './BaseRegistryArticle';