@dxos/plugin-registry 0.8.4-main.fd6878d → 0.8.4-staging.60fe92afc8

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 (237) 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/neutral/RegistryPlugin.node.mjs +153 -0
  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 +3 -1
  22. package/dist/types/src/RegistryPlugin.d.ts.map +1 -1
  23. package/dist/types/src/RegistryPlugin.node.d.ts +4 -0
  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 +4 -2
  30. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  31. package/dist/types/src/capabilities/dev-plugin-loader.d.ts +11 -0
  32. package/dist/types/src/capabilities/dev-plugin-loader.d.ts.map +1 -0
  33. package/dist/types/src/capabilities/index.d.ts +15 -3
  34. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  35. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  36. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  37. package/dist/types/src/capabilities/react-surface.d.ts +3 -2
  38. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  39. package/dist/types/src/capabilities/settings.d.ts +14 -0
  40. package/dist/types/src/capabilities/settings.d.ts.map +1 -0
  41. package/dist/types/src/categories.d.ts +18 -0
  42. package/dist/types/src/categories.d.ts.map +1 -0
  43. package/dist/types/src/commands/disable.d.ts +11 -0
  44. package/dist/types/src/commands/disable.d.ts.map +1 -0
  45. package/dist/types/src/commands/enable.d.ts +11 -0
  46. package/dist/types/src/commands/enable.d.ts.map +1 -0
  47. package/dist/types/src/commands/index.d.ts +3 -0
  48. package/dist/types/src/commands/index.d.ts.map +1 -0
  49. package/dist/types/src/commands/list.d.ts +7 -0
  50. package/dist/types/src/commands/list.d.ts.map +1 -0
  51. package/dist/types/src/commands/util.d.ts +11 -0
  52. package/dist/types/src/commands/util.d.ts.map +1 -0
  53. package/dist/types/src/components/DisableDependentsAlert/DisableDependentsAlert.d.ts +24 -0
  54. package/dist/types/src/components/DisableDependentsAlert/DisableDependentsAlert.d.ts.map +1 -0
  55. package/dist/types/src/components/DisableDependentsAlert/index.d.ts +2 -0
  56. package/dist/types/src/components/DisableDependentsAlert/index.d.ts.map +1 -0
  57. package/dist/types/src/components/PluginDetail/PluginDetail.d.ts +82 -0
  58. package/dist/types/src/components/PluginDetail/PluginDetail.d.ts.map +1 -0
  59. package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts +79 -0
  60. package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts.map +1 -0
  61. package/dist/types/src/components/PluginDetail/index.d.ts +2 -0
  62. package/dist/types/src/components/PluginDetail/index.d.ts.map +1 -0
  63. package/dist/types/src/components/PluginFailureBadge/PluginFailureBadge.d.ts +20 -0
  64. package/dist/types/src/components/PluginFailureBadge/PluginFailureBadge.d.ts.map +1 -0
  65. package/dist/types/src/components/PluginFailureBadge/index.d.ts +2 -0
  66. package/dist/types/src/components/PluginFailureBadge/index.d.ts.map +1 -0
  67. package/dist/types/src/components/PluginList/PluginItem.d.ts +40 -0
  68. package/dist/types/src/components/PluginList/PluginItem.d.ts.map +1 -0
  69. package/dist/types/src/components/PluginList/PluginList.d.ts +23 -0
  70. package/dist/types/src/components/PluginList/PluginList.d.ts.map +1 -0
  71. package/dist/types/src/components/PluginList/PluginList.stories.d.ts +78 -0
  72. package/dist/types/src/components/PluginList/PluginList.stories.d.ts.map +1 -0
  73. package/dist/types/src/components/PluginList/index.d.ts +2 -0
  74. package/dist/types/src/components/PluginList/index.d.ts.map +1 -0
  75. package/dist/types/src/components/RegistrySettings/RegistrySettings.d.ts +18 -0
  76. package/dist/types/src/components/RegistrySettings/RegistrySettings.d.ts.map +1 -0
  77. package/dist/types/src/components/RegistrySettings/index.d.ts +2 -0
  78. package/dist/types/src/components/RegistrySettings/index.d.ts.map +1 -0
  79. package/dist/types/src/components/index.d.ts +3 -1
  80. package/dist/types/src/components/index.d.ts.map +1 -1
  81. package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts +20 -0
  82. package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts.map +1 -0
  83. package/dist/types/src/containers/BaseRegistryArticle/index.d.ts +2 -0
  84. package/dist/types/src/containers/BaseRegistryArticle/index.d.ts.map +1 -0
  85. package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts +5 -0
  86. package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts.map +1 -0
  87. package/dist/types/src/containers/LoadPluginDialog/index.d.ts +2 -0
  88. package/dist/types/src/containers/LoadPluginDialog/index.d.ts.map +1 -0
  89. package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts +7 -0
  90. package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts.map +1 -0
  91. package/dist/types/src/containers/PluginArticle/index.d.ts +2 -0
  92. package/dist/types/src/containers/PluginArticle/index.d.ts.map +1 -0
  93. package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts +8 -0
  94. package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts.map +1 -0
  95. package/dist/types/src/containers/PublicRegistryArticle/index.d.ts +2 -0
  96. package/dist/types/src/containers/PublicRegistryArticle/index.d.ts.map +1 -0
  97. package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts +14 -0
  98. package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts.map +1 -0
  99. package/dist/types/src/containers/RegistryArticle/index.d.ts +2 -0
  100. package/dist/types/src/containers/RegistryArticle/index.d.ts.map +1 -0
  101. package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts +13 -0
  102. package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts.map +1 -0
  103. package/dist/types/src/containers/RegistrySettingsContainer/index.d.ts +2 -0
  104. package/dist/types/src/containers/RegistrySettingsContainer/index.d.ts.map +1 -0
  105. package/dist/types/src/containers/index.d.ts +8 -0
  106. package/dist/types/src/containers/index.d.ts.map +1 -0
  107. package/dist/types/src/hooks/index.d.ts +9 -0
  108. package/dist/types/src/hooks/index.d.ts.map +1 -0
  109. package/dist/types/src/hooks/useAutoTags.d.ts +20 -0
  110. package/dist/types/src/hooks/useAutoTags.d.ts.map +1 -0
  111. package/dist/types/src/hooks/useCatalogEntry.d.ts +11 -0
  112. package/dist/types/src/hooks/useCatalogEntry.d.ts.map +1 -0
  113. package/dist/types/src/hooks/useDisableConfirmation.d.ts +19 -0
  114. package/dist/types/src/hooks/useDisableConfirmation.d.ts.map +1 -0
  115. package/dist/types/src/hooks/useInstalledVersionTag.d.ts +18 -0
  116. package/dist/types/src/hooks/useInstalledVersionTag.d.ts.map +1 -0
  117. package/dist/types/src/hooks/usePluginActions.d.ts +36 -0
  118. package/dist/types/src/hooks/usePluginActions.d.ts.map +1 -0
  119. package/dist/types/src/hooks/useRegistryPlugins.d.ts +15 -0
  120. package/dist/types/src/hooks/useRegistryPlugins.d.ts.map +1 -0
  121. package/dist/types/src/hooks/useUpdateAvailable.d.ts +18 -0
  122. package/dist/types/src/hooks/useUpdateAvailable.d.ts.map +1 -0
  123. package/dist/types/src/hooks/useVersionPicker.d.ts +21 -0
  124. package/dist/types/src/hooks/useVersionPicker.d.ts.map +1 -0
  125. package/dist/types/src/index.d.ts +1 -1
  126. package/dist/types/src/index.d.ts.map +1 -1
  127. package/dist/types/src/meta.d.ts +21 -4
  128. package/dist/types/src/meta.d.ts.map +1 -1
  129. package/dist/types/src/operations/index.d.ts +3 -0
  130. package/dist/types/src/operations/index.d.ts.map +1 -0
  131. package/dist/types/src/operations/open-plugin-registry.d.ts +5 -0
  132. package/dist/types/src/operations/open-plugin-registry.d.ts.map +1 -0
  133. package/dist/types/src/plugin.d.ts +4 -0
  134. package/dist/types/src/plugin.d.ts.map +1 -0
  135. package/dist/types/src/storage.d.ts +18 -0
  136. package/dist/types/src/storage.d.ts.map +1 -0
  137. package/dist/types/src/translations.d.ts +59 -16
  138. package/dist/types/src/translations.d.ts.map +1 -1
  139. package/dist/types/src/types.d.ts +32 -1
  140. package/dist/types/src/types.d.ts.map +1 -1
  141. package/dist/types/tsconfig.tsbuildinfo +1 -1
  142. package/package.json +76 -37
  143. package/src/RegistryPlugin.node.ts +19 -0
  144. package/src/RegistryPlugin.test.ts +24 -0
  145. package/src/RegistryPlugin.tsx +20 -27
  146. package/src/RegistryPlugin.workerd.ts +11 -0
  147. package/src/capabilities/app-graph-builder.ts +199 -135
  148. package/src/capabilities/dev-plugin-loader.ts +39 -0
  149. package/src/capabilities/index.ts +10 -4
  150. package/src/capabilities/operation-handler.ts +16 -0
  151. package/src/capabilities/react-surface.tsx +115 -79
  152. package/src/capabilities/settings.ts +34 -0
  153. package/src/categories.ts +44 -0
  154. package/src/commands/disable.ts +47 -0
  155. package/src/commands/enable.ts +44 -0
  156. package/src/commands/index.ts +14 -0
  157. package/src/commands/list.ts +41 -0
  158. package/src/commands/util.ts +24 -0
  159. package/src/components/DisableDependentsAlert/DisableDependentsAlert.tsx +69 -0
  160. package/src/components/DisableDependentsAlert/index.ts +5 -0
  161. package/src/components/PluginDetail/PluginDetail.stories.tsx +48 -0
  162. package/src/components/PluginDetail/PluginDetail.tsx +345 -0
  163. package/src/components/PluginDetail/index.ts +5 -0
  164. package/src/components/PluginFailureBadge/PluginFailureBadge.tsx +68 -0
  165. package/src/components/PluginFailureBadge/index.ts +5 -0
  166. package/src/components/PluginList/PluginItem.tsx +223 -0
  167. package/src/components/PluginList/PluginList.stories.tsx +90 -0
  168. package/src/components/PluginList/PluginList.tsx +52 -0
  169. package/src/components/PluginList/index.ts +5 -0
  170. package/src/components/RegistrySettings/RegistrySettings.tsx +120 -0
  171. package/src/components/RegistrySettings/index.ts +5 -0
  172. package/src/components/index.ts +3 -1
  173. package/src/containers/BaseRegistryArticle/BaseRegistryArticle.tsx +178 -0
  174. package/src/containers/BaseRegistryArticle/index.ts +5 -0
  175. package/src/containers/LoadPluginDialog/LoadPluginDialog.tsx +88 -0
  176. package/src/containers/LoadPluginDialog/index.ts +5 -0
  177. package/src/containers/PluginArticle/PluginArticle.tsx +117 -0
  178. package/src/containers/PluginArticle/index.ts +5 -0
  179. package/src/containers/PublicRegistryArticle/PublicRegistryArticle.tsx +193 -0
  180. package/src/containers/PublicRegistryArticle/index.ts +5 -0
  181. package/src/containers/RegistryArticle/RegistryArticle.tsx +53 -0
  182. package/src/containers/RegistryArticle/index.ts +5 -0
  183. package/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.tsx +59 -0
  184. package/src/containers/RegistrySettingsContainer/index.ts +5 -0
  185. package/src/containers/index.ts +12 -0
  186. package/src/hooks/index.ts +12 -0
  187. package/src/hooks/useAutoTags.ts +52 -0
  188. package/src/hooks/useCatalogEntry.ts +25 -0
  189. package/src/hooks/useDisableConfirmation.ts +58 -0
  190. package/src/hooks/useInstalledVersionTag.ts +41 -0
  191. package/src/hooks/usePluginActions.ts +177 -0
  192. package/src/hooks/useRegistryPlugins.ts +29 -0
  193. package/src/hooks/useUpdateAvailable.ts +31 -0
  194. package/src/hooks/useVersionPicker.ts +112 -0
  195. package/src/index.ts +1 -1
  196. package/src/meta.ts +39 -10
  197. package/src/operations/index.ts +7 -0
  198. package/src/operations/open-plugin-registry.ts +21 -0
  199. package/src/plugin.ts +11 -0
  200. package/src/storage.ts +50 -0
  201. package/src/translations.ts +64 -15
  202. package/src/types.ts +37 -1
  203. package/dist/lib/browser/app-graph-builder-6JQNRTEQ.mjs +0 -161
  204. package/dist/lib/browser/app-graph-builder-6JQNRTEQ.mjs.map +0 -7
  205. package/dist/lib/browser/chunk-AEBASNOI.mjs +0 -16
  206. package/dist/lib/browser/chunk-AEBASNOI.mjs.map +0 -7
  207. package/dist/lib/browser/index.mjs +0 -71
  208. package/dist/lib/browser/index.mjs.map +0 -7
  209. package/dist/lib/browser/intent-resolver-UGDFPMAG.mjs +0 -23
  210. package/dist/lib/browser/intent-resolver-UGDFPMAG.mjs.map +0 -7
  211. package/dist/lib/browser/meta.json +0 -1
  212. package/dist/lib/browser/react-surface-BQPO3F3E.mjs +0 -333
  213. package/dist/lib/browser/react-surface-BQPO3F3E.mjs.map +0 -7
  214. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  215. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  216. package/dist/types/src/components/Icon.stories.d.ts +0 -6
  217. package/dist/types/src/components/Icon.stories.d.ts.map +0 -1
  218. package/dist/types/src/components/PluginDetail.d.ts +0 -9
  219. package/dist/types/src/components/PluginDetail.d.ts.map +0 -1
  220. package/dist/types/src/components/PluginDetail.stories.d.ts +0 -8
  221. package/dist/types/src/components/PluginDetail.stories.d.ts.map +0 -1
  222. package/dist/types/src/components/PluginItem.d.ts +0 -13
  223. package/dist/types/src/components/PluginItem.d.ts.map +0 -1
  224. package/dist/types/src/components/PluginList.d.ts +0 -8
  225. package/dist/types/src/components/PluginList.d.ts.map +0 -1
  226. package/dist/types/src/components/PluginList.stories.d.ts +0 -11
  227. package/dist/types/src/components/PluginList.stories.d.ts.map +0 -1
  228. package/dist/types/src/components/RegistryContainer.d.ts +0 -7
  229. package/dist/types/src/components/RegistryContainer.d.ts.map +0 -1
  230. package/src/capabilities/intent-resolver.ts +0 -27
  231. package/src/components/Icon.stories.tsx +0 -108
  232. package/src/components/PluginDetail.stories.tsx +0 -46
  233. package/src/components/PluginDetail.tsx +0 -63
  234. package/src/components/PluginItem.tsx +0 -119
  235. package/src/components/PluginList.stories.tsx +0 -83
  236. package/src/components/PluginList.tsx +0 -26
  237. package/src/components/RegistryContainer.tsx +0 -84
@@ -2,8 +2,14 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { lazy } from '@dxos/app-framework';
5
+ import { Capability } from '@dxos/app-framework';
6
+ import { OperationHandlerSet } from '@dxos/compute';
6
7
 
7
- export const AppGraphBuilder = lazy(() => import('./app-graph-builder'));
8
- export const IntentResolver = lazy(() => import('./intent-resolver'));
9
- export const ReactSurface = lazy(() => import('./react-surface'));
8
+ export const AppGraphBuilder = Capability.lazy('AppGraphBuilder', () => import('./app-graph-builder'));
9
+ export const DevPluginLoader = Capability.lazy('DevPluginLoader', () => import('./dev-plugin-loader'));
10
+ export const OperationHandler = Capability.lazy<OperationHandlerSet.OperationHandlerSet>(
11
+ 'OperationHandler',
12
+ () => import('./operation-handler'),
13
+ );
14
+ export const ReactSurface = Capability.lazy('ReactSurface', () => import('./react-surface'));
15
+ export const RegistrySettings = Capability.lazy('RegistrySettings', () => import('./settings'));
@@ -0,0 +1,16 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capabilities, Capability } from '@dxos/app-framework';
8
+ import type { OperationHandlerSet } from '@dxos/compute';
9
+
10
+ import { RegistryOperationHandlerSet } from '#operations';
11
+
12
+ export default Capability.makeModule<OperationHandlerSet.OperationHandlerSet>(
13
+ Effect.fnUntraced(function* () {
14
+ return Capability.contributes(Capabilities.OperationHandler, RegistryOperationHandlerSet);
15
+ }),
16
+ );
@@ -2,89 +2,125 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import React, { useCallback, useMemo } from 'react';
5
+ import * as Effect from 'effect/Effect';
6
+ import React, { type ComponentProps, useMemo } from 'react';
6
7
 
7
- import { Capabilities, Plugin, contributes, createSurface, usePluginManager } from '@dxos/app-framework';
8
- import { StackItem } from '@dxos/react-ui-stack';
8
+ import { Capabilities, Capability, Plugin } from '@dxos/app-framework';
9
+ import { Surface, usePluginManager } from '@dxos/app-framework/ui';
10
+ import { AppCapabilities } from '@dxos/app-toolkit';
11
+ import { AppSurface } from '@dxos/app-toolkit/ui';
9
12
 
10
- import { PluginDetail, RegistryContainer } from '../components';
11
- import { REGISTRY_KEY, REGISTRY_PLUGIN } from '../meta';
13
+ import { DisableDependentsAlert } from '#components';
14
+ import {
15
+ LOAD_PLUGIN_DIALOG,
16
+ LoadPluginDialog,
17
+ PluginArticle,
18
+ PublicRegistryArticle,
19
+ RegistryArticle,
20
+ RegistrySettingsContainer,
21
+ } from '#containers';
22
+ import { DISABLE_DEPENDENTS_DIALOG, meta, registryCategoryId } from '#meta';
12
23
 
13
- export default () =>
14
- contributes(Capabilities.ReactSurface, [
15
- createSurface({
16
- id: `${REGISTRY_PLUGIN}/all`,
17
- role: 'article',
18
- filter: (data): data is any => data.subject === `${REGISTRY_KEY}+all`,
19
- component: () => {
20
- const manager = usePluginManager();
21
- const filtered = useMemo(() => manager.plugins.filter(({ meta }) => !manager.core.includes(meta.id)), []);
24
+ import { type PluginPredicate, getCategoryPredicate } from '../categories';
25
+ import { useAutoTags, useRegistryPlugins, useRemotePluginIds } from '../hooks';
22
26
 
23
- return <RegistryContainer id={`${REGISTRY_KEY}+all`} plugins={filtered} />;
24
- },
25
- }),
26
- createSurface({
27
- id: `${REGISTRY_PLUGIN}/installed`,
28
- role: 'article',
29
- filter: (data): data is any => data.subject === `${REGISTRY_KEY}+installed`,
30
- component: () => {
31
- const manager = usePluginManager();
32
- const filtered = useMemo(
33
- () =>
34
- manager.plugins
35
- .filter(({ meta }) => !manager.core.includes(meta.id))
36
- .filter(({ meta }) => manager.enabled.includes(meta.id)),
37
- [],
38
- );
27
+ export default Capability.makeModule(() =>
28
+ Effect.succeed(
29
+ Capability.contributes(Capabilities.ReactSurface, [
30
+ Surface.create({
31
+ id: 'bundled',
32
+ filter: AppSurface.literal(AppSurface.Article, registryCategoryId('bundled')),
33
+ component: () => {
34
+ const predicate = useCategoryPredicate(registryCategoryId('bundled'));
35
+ return <FilteredRegistryArticle id={registryCategoryId('bundled')} filter={predicate} />;
36
+ },
37
+ }),
38
+ Surface.create({
39
+ id: 'installed',
40
+ filter: AppSurface.literal(AppSurface.Article, registryCategoryId('installed')),
41
+ component: () => {
42
+ const predicate = useCategoryPredicate(registryCategoryId('installed'));
43
+ return <FilteredRegistryArticle id={registryCategoryId('installed')} filter={predicate} />;
44
+ },
45
+ }),
46
+ Surface.create({
47
+ id: 'recommended',
48
+ filter: AppSurface.literal(AppSurface.Article, registryCategoryId('recommended')),
49
+ component: () => {
50
+ const predicate = useCategoryPredicate(registryCategoryId('recommended'));
51
+ return <FilteredRegistryArticle id={registryCategoryId('recommended')} filter={predicate} />;
52
+ },
53
+ }),
54
+ Surface.create({
55
+ id: 'labs',
56
+ filter: AppSurface.literal(AppSurface.Article, registryCategoryId('labs')),
57
+ component: () => {
58
+ const predicate = useCategoryPredicate(registryCategoryId('labs'));
59
+ return <FilteredRegistryArticle id={registryCategoryId('labs')} filter={predicate} />;
60
+ },
61
+ }),
62
+ Surface.create({
63
+ id: 'registry',
64
+ filter: AppSurface.literal(AppSurface.Article, registryCategoryId('registry')),
65
+ component: () => <PublicRegistryArticle id={registryCategoryId('registry')} />,
66
+ }),
67
+ Surface.create({
68
+ id: 'pluginDetails',
69
+ filter: AppSurface.subject(AppSurface.Article, Plugin.isPlugin),
70
+ component: ({ data: { subject } }) => {
71
+ return <PluginArticle subject={subject} />;
72
+ },
73
+ }),
74
+ Surface.create({
75
+ id: LOAD_PLUGIN_DIALOG,
76
+ filter: AppSurface.component(AppSurface.Dialog, LOAD_PLUGIN_DIALOG),
77
+ component: () => <LoadPluginDialog />,
78
+ }),
79
+ Surface.create({
80
+ id: DISABLE_DEPENDENTS_DIALOG,
81
+ filter: AppSurface.component<ComponentProps<typeof DisableDependentsAlert>>(
82
+ AppSurface.Dialog,
83
+ DISABLE_DEPENDENTS_DIALOG,
84
+ ),
85
+ component: ({ data }) => <DisableDependentsAlert {...data.props} />,
86
+ }),
87
+ Surface.create({
88
+ id: 'pluginSettings',
89
+ role: 'article',
90
+ filter: (data): data is { subject: AppCapabilities.Settings } =>
91
+ AppCapabilities.isSettings(data.subject) && data.subject.prefix === meta.id,
92
+ component: ({ data: { subject } }) => <RegistrySettingsContainer subject={subject} />,
93
+ }),
94
+ ]),
95
+ ),
96
+ );
39
97
 
40
- return <RegistryContainer id={`${REGISTRY_KEY}+installed`} plugins={filtered} />;
41
- },
42
- }),
43
- createSurface({
44
- id: `${REGISTRY_PLUGIN}/recommended`,
45
- role: 'article',
46
- filter: (data): data is any => data.subject === `${REGISTRY_KEY}+recommended`,
47
- component: () => {
48
- const manager = usePluginManager();
49
- const filtered = useMemo(
50
- () =>
51
- manager.plugins
52
- .filter(({ meta }) => !manager.core.includes(meta.id))
53
- .filter(({ meta }) => !meta.tags?.includes('labs')),
54
- [],
55
- );
98
+ /**
99
+ * Resolves the {@link PluginPredicate} for a registry category against the live plugin list.
100
+ * Shared with the graph builder via {@link getCategoryPredicate} so the category lists and their counts agree.
101
+ */
102
+ const useCategoryPredicate = (category: string): PluginPredicate => {
103
+ const manager = usePluginManager();
104
+ const remoteIds = useRemotePluginIds();
105
+ const core = useMemo(() => manager.getCore(), [manager]);
106
+ const enabled = useMemo(() => manager.getEnabled(), [manager]);
107
+ return useMemo(
108
+ () => getCategoryPredicate(category, { core, enabled, remoteIds }),
109
+ [category, core, enabled, remoteIds],
110
+ );
111
+ };
56
112
 
57
- return <RegistryContainer id={`${REGISTRY_KEY}+recommended`} plugins={filtered} />;
58
- },
59
- }),
60
- createSurface({
61
- id: `${REGISTRY_PLUGIN}/labs`,
62
- role: 'article',
63
- filter: (data): data is any => data.subject === `${REGISTRY_KEY}+labs`,
64
- component: () => {
65
- const manager = usePluginManager();
66
- const filtered = useMemo(() => manager.plugins.filter(({ meta }) => meta.tags?.includes('labs')), []);
113
+ /**
114
+ * Renders the {@link RegistryArticle} surface filtered by an arbitrary
115
+ * predicate computed against the live plugin list. Centralises the
116
+ * `usePluginManager` + `useRegistryPlugins` + `useAutoTags` wiring shared
117
+ * by every category surface.
118
+ */
119
+ const FilteredRegistryArticle = ({ id, filter }: { id: string; filter: PluginPredicate }) => {
120
+ const manager = usePluginManager();
121
+ const { entries } = useRegistryPlugins();
122
+ const extraTagsById = useAutoTags(entries);
123
+ const filtered = useMemo(() => manager.getPlugins().filter(filter), [manager, filter]);
67
124
 
68
- return <RegistryContainer id={`${REGISTRY_KEY}+labs`} plugins={filtered} />;
69
- },
70
- }),
71
- createSurface({
72
- id: `${REGISTRY_PLUGIN}/plugin-details`,
73
- role: 'article',
74
- filter: (data): data is { subject: Plugin } => data.subject instanceof Plugin,
75
- component: ({ data: { subject } }) => {
76
- const manager = usePluginManager();
77
- const enabled = manager.enabled.includes(subject.meta.id);
78
- const handleEnable = useCallback(
79
- () => (enabled ? manager.disable(subject.meta.id) : manager.enable(subject.meta.id)),
80
- [manager, subject.meta.id, enabled],
81
- );
82
-
83
- return (
84
- <StackItem.Content>
85
- <PluginDetail plugin={subject} enabled={enabled} onEnable={handleEnable} />
86
- </StackItem.Content>
87
- );
88
- },
89
- }),
90
- ]);
125
+ return <RegistryArticle id={id} plugins={filtered} extraTagsById={extraTagsById} />;
126
+ };
@@ -0,0 +1,34 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capability, PLUGIN_DEV_SERVER_PORT } from '@dxos/app-framework';
8
+ import { AppCapabilities } from '@dxos/app-toolkit';
9
+ import { createKvsStore } from '@dxos/effect';
10
+
11
+ import { meta } from '#meta';
12
+
13
+ import { RegistryCapabilities, RegistrySettingsSchema } from '../types';
14
+
15
+ const DEFAULT_DEV_PLUGIN_URL = `http://localhost:${PLUGIN_DEV_SERVER_PORT}/manifest.json`;
16
+
17
+ export default Capability.makeModule(() =>
18
+ Effect.sync(() => {
19
+ const settingsAtom = createKvsStore({
20
+ key: meta.id,
21
+ schema: RegistrySettingsSchema,
22
+ defaultValue: () => ({ devPluginUrl: DEFAULT_DEV_PLUGIN_URL }),
23
+ });
24
+
25
+ return [
26
+ Capability.contributes(RegistryCapabilities.Settings, settingsAtom),
27
+ Capability.contributes(AppCapabilities.Settings, {
28
+ prefix: meta.id,
29
+ schema: RegistrySettingsSchema,
30
+ atom: settingsAtom,
31
+ }),
32
+ ];
33
+ }),
34
+ );
@@ -0,0 +1,44 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type Plugin, UrlLoader } from '@dxos/app-framework';
6
+
7
+ import { registryCategoryId } from './meta';
8
+
9
+ export type PluginPredicate = (plugin: Plugin.Plugin) => boolean;
10
+
11
+ export type CategoryFilterContext = {
12
+ /** Core (bundled-by-default) plugin ids. */
13
+ core: readonly string[];
14
+ /** Enabled plugin ids. */
15
+ enabled: readonly string[];
16
+ /** Plugin ids loaded from a remote URL (not bundled). */
17
+ remoteIds: ReadonlySet<string>;
18
+ };
19
+
20
+ /**
21
+ * Predicate selecting the plugins shown in a registry category, computed against the live plugin list.
22
+ * The `registry` category is sourced from the catalog rather than the local plugin list and is not handled here.
23
+ */
24
+ export const getCategoryPredicate = (
25
+ category: string,
26
+ { core, enabled, remoteIds }: CategoryFilterContext,
27
+ ): PluginPredicate => {
28
+ switch (category) {
29
+ case registryCategoryId('bundled'):
30
+ return ({ meta }) => !core.includes(meta.id) && !remoteIds.has(meta.id);
31
+ case registryCategoryId('installed'):
32
+ return ({ meta }) => !core.includes(meta.id) && enabled.includes(meta.id);
33
+ case registryCategoryId('recommended'):
34
+ return ({ meta }) => !core.includes(meta.id) && !remoteIds.has(meta.id) && !meta.tags?.includes('labs');
35
+ case registryCategoryId('labs'):
36
+ return ({ meta }) => meta.tags?.includes('labs') ?? false;
37
+ default:
38
+ return () => false;
39
+ }
40
+ };
41
+
42
+ /** Set of plugin ids known to originate from a remote URL (not bundled). */
43
+ export const getRemotePluginIds = (): ReadonlySet<string> =>
44
+ new Set(UrlLoader.getRemoteEntries().map((entry) => entry.id));
@@ -0,0 +1,47 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Args from '@effect/cli/Args';
6
+ import * as Command from '@effect/cli/Command';
7
+ import * as Console from 'effect/Console';
8
+ import * as Effect from 'effect/Effect';
9
+
10
+ import { Plugin } from '@dxos/app-framework';
11
+ import { CommandConfig } from '@dxos/cli-util';
12
+ import { invariant } from '@dxos/invariant';
13
+
14
+ import { saveEnabledPlugins } from '../storage';
15
+
16
+ export const handler = Effect.fn(function* ({ id }: { id: string }) {
17
+ const { json, profile } = yield* CommandConfig;
18
+ const manager = yield* Plugin.Service;
19
+
20
+ const plugins = manager.getPlugins();
21
+ const plugin = plugins.find((p: Plugin.Plugin) => p.meta.id === id);
22
+ invariant(plugin, `Plugin not found: ${id}`);
23
+
24
+ const core = manager.getCore();
25
+ invariant(!core.includes(id), `Cannot disable core plugin: ${id}`);
26
+
27
+ const disabled = yield* manager.disable(id);
28
+ invariant(disabled, `Failed to disable plugin: ${id}`);
29
+
30
+ // Save enabled plugins to storage
31
+ const enabledPlugins = manager.getEnabled();
32
+ yield* saveEnabledPlugins({ profile, enabled: [...enabledPlugins] });
33
+
34
+ if (json) {
35
+ yield* Console.log(JSON.stringify({ id, enabled: false }, null, 2));
36
+ } else {
37
+ yield* Console.log(`Plugin "${plugin.meta.name ?? id}" disabled.`);
38
+ }
39
+ });
40
+
41
+ export const disable = Command.make(
42
+ 'disable',
43
+ {
44
+ id: Args.text({ name: 'id' }).pipe(Args.withDescription('The ID of the plugin to disable.')),
45
+ },
46
+ handler,
47
+ ).pipe(Command.withDescription('Disable a plugin.'));
@@ -0,0 +1,44 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Args from '@effect/cli/Args';
6
+ import * as Command from '@effect/cli/Command';
7
+ import * as Console from 'effect/Console';
8
+ import * as Effect from 'effect/Effect';
9
+
10
+ import { Plugin } from '@dxos/app-framework';
11
+ import { CommandConfig } from '@dxos/cli-util';
12
+ import { invariant } from '@dxos/invariant';
13
+
14
+ import { saveEnabledPlugins } from '../storage';
15
+
16
+ export const handler = Effect.fn(function* ({ id }: { id: string }) {
17
+ const { json, profile } = yield* CommandConfig;
18
+ const manager = yield* Plugin.Service;
19
+
20
+ const plugins = manager.getPlugins();
21
+ const plugin = plugins.find((p: Plugin.Plugin) => p.meta.id === id);
22
+ invariant(plugin, `Plugin not found: ${id}`);
23
+
24
+ const enabled = yield* manager.enable(id);
25
+ invariant(enabled, `Failed to enable plugin: ${id}`);
26
+
27
+ // Save enabled plugins to storage
28
+ const enabledPlugins = manager.getEnabled();
29
+ yield* saveEnabledPlugins({ profile, enabled: [...enabledPlugins] });
30
+
31
+ if (json) {
32
+ yield* Console.log(JSON.stringify({ id, enabled: true }, null, 2));
33
+ } else {
34
+ yield* Console.log(`Plugin "${plugin.meta.name ?? id}" enabled.`);
35
+ }
36
+ });
37
+
38
+ export const enable = Command.make(
39
+ 'enable',
40
+ {
41
+ id: Args.text({ name: 'id' }).pipe(Args.withDescription('The ID of the plugin to enable.')),
42
+ },
43
+ handler,
44
+ ).pipe(Command.withDescription('Enable a plugin.'));
@@ -0,0 +1,14 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Command from '@effect/cli/Command';
6
+
7
+ import { disable } from './disable';
8
+ import { enable } from './enable';
9
+ import { list } from './list';
10
+
11
+ export const plugin: Command.Command<any, any, any, any> = Command.make('plugin').pipe(
12
+ Command.withDescription('Manage plugins.'),
13
+ Command.withSubcommands([enable, disable, list]),
14
+ );
@@ -0,0 +1,41 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Command from '@effect/cli/Command';
6
+ import * as Console from 'effect/Console';
7
+ import * as Effect from 'effect/Effect';
8
+
9
+ import { Plugin } from '@dxos/app-framework';
10
+ import { CommandConfig, printList } from '@dxos/cli-util';
11
+
12
+ import { type FormattedPlugin, printPlugin } from './util';
13
+
14
+ export const handler = Effect.fn(function* () {
15
+ const { json } = yield* CommandConfig;
16
+ const manager = yield* Plugin.Service;
17
+
18
+ const plugins = manager.getPlugins();
19
+ const enabled = manager.getEnabled();
20
+ const core = manager.getCore();
21
+
22
+ const formattedPlugins: FormattedPlugin[] = plugins.map((plugin: Plugin.Plugin) => {
23
+ const isEnabled = enabled.includes(plugin.meta.id);
24
+ const isCore = core.includes(plugin.meta.id);
25
+ return {
26
+ id: plugin.meta.id,
27
+ name: plugin.meta.name ?? plugin.meta.id,
28
+ enabled: isEnabled,
29
+ core: isCore,
30
+ };
31
+ });
32
+
33
+ if (json) {
34
+ yield* Console.log(JSON.stringify(formattedPlugins, null, 2));
35
+ } else {
36
+ const formatted = formattedPlugins.map(printPlugin);
37
+ yield* Console.log(printList(formatted));
38
+ }
39
+ });
40
+
41
+ export const list = Command.make('list', {}, handler).pipe(Command.withDescription('List all available plugins.'));
@@ -0,0 +1,24 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { FormBuilder } from '@dxos/cli-util';
6
+
7
+ export type FormattedPlugin = {
8
+ id: string;
9
+ name: string;
10
+ enabled: boolean;
11
+ core: boolean;
12
+ };
13
+
14
+ /**
15
+ * Pretty prints a plugin with ANSI colors.
16
+ */
17
+ export const printPlugin = (plugin: FormattedPlugin) => {
18
+ const status = plugin.core ? 'core' : plugin.enabled ? 'enabled' : 'disabled';
19
+ return FormBuilder.make({ title: plugin.name }).pipe(
20
+ FormBuilder.set('id', plugin.id),
21
+ FormBuilder.set('status', status),
22
+ FormBuilder.build,
23
+ );
24
+ };
@@ -0,0 +1,69 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { AlertDialog, Button, useTranslation } from '@dxos/react-ui';
8
+
9
+ import { meta } from '#meta';
10
+
11
+ export type DisableDependentsAlertProps = {
12
+ /** Id of the plugin the user requested to disable. */
13
+ pluginId: string;
14
+ /** Ids of the currently-enabled dependents that would also be disabled. */
15
+ dependents: readonly string[];
16
+ /**
17
+ * Resolves a plugin id to its display name. The component delegates this so
18
+ * the parent can choose how to source names (typically the registered
19
+ * `Plugin.Meta.name`). Falls back to the id when omitted.
20
+ */
21
+ onResolvePluginName?: (pluginId: string) => string;
22
+ onConfirm: () => void;
23
+ };
24
+
25
+ /**
26
+ * Confirmation prompt shown when a user toggles off a plugin that has
27
+ * currently-enabled dependents. Rendered inside the layout's dialog surface
28
+ * (via `LayoutOperation.UpdateDialog` with `type: 'alert'`), which provides
29
+ * the `AlertDialog.Root` / `Overlay` wrappers — this component renders only
30
+ * the content. `onConfirm` runs the cascade disable and closes the dialog;
31
+ * cancel is handled by the layout's open-change wiring.
32
+ */
33
+ export const DisableDependentsAlert = ({
34
+ pluginId,
35
+ dependents,
36
+ onResolvePluginName,
37
+ onConfirm,
38
+ }: DisableDependentsAlertProps) => {
39
+ const { t } = useTranslation(meta.id);
40
+ const resolveName = onResolvePluginName ?? ((id: string) => id);
41
+ return (
42
+ <AlertDialog.Content>
43
+ <AlertDialog.Body>
44
+ <AlertDialog.Title>{t('disable-dependents-dialog.title')}</AlertDialog.Title>
45
+ <AlertDialog.Description>
46
+ {t('disable-dependents-dialog.description', { plugin: resolveName(pluginId) })}
47
+ </AlertDialog.Description>
48
+ <ul className='mt-2 list-disc pl-6 text-sm text-description'>
49
+ {dependents.map((dependentId) => (
50
+ <li key={dependentId} title={dependentId}>
51
+ {resolveName(dependentId)}
52
+ </li>
53
+ ))}
54
+ </ul>
55
+ </AlertDialog.Body>
56
+ <AlertDialog.ActionBar>
57
+ <div className='grow' />
58
+ <AlertDialog.Cancel asChild>
59
+ <Button>{t('cancel.label')}</Button>
60
+ </AlertDialog.Cancel>
61
+ <AlertDialog.Action asChild>
62
+ <Button variant='primary' onClick={onConfirm}>
63
+ {t('disable-dependents-dialog.confirm.label')}
64
+ </Button>
65
+ </AlertDialog.Action>
66
+ </AlertDialog.ActionBar>
67
+ </AlertDialog.Content>
68
+ );
69
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './DisableDependentsAlert';
@@ -0,0 +1,48 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+
7
+ import { Plugin } from '@dxos/app-framework';
8
+ import { DXN } from '@dxos/keys';
9
+ import { random } from '@dxos/random';
10
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
11
+
12
+ import { translations } from '#translations';
13
+
14
+ import { PluginDetail } from './PluginDetail';
15
+
16
+ const meta = {
17
+ title: 'plugins/plugin-registry/components/PluginDetail',
18
+ component: PluginDetail,
19
+ decorators: [withTheme(), withLayout({ layout: 'column' })],
20
+ parameters: {
21
+ layout: 'fullscreen',
22
+ translations,
23
+ },
24
+ } satisfies Meta<typeof PluginDetail>;
25
+
26
+ export default meta;
27
+
28
+ type Story = StoryObj<typeof meta>;
29
+
30
+ export const Default: Story = {
31
+ args: {
32
+ plugin: Plugin.define(
33
+ Plugin.makeMeta({
34
+ key: DXN.make('com.example.plugin.test'),
35
+ name: 'Test Plugin',
36
+ author: 'DXOS',
37
+ description: random.lorem.paragraphs(2),
38
+ icon: 'ph--bug--regular',
39
+ iconHue: 'sky',
40
+ homePage: 'https://example.com',
41
+ source: 'https://github.com/example/test-plugin',
42
+ screenshots: [
43
+ 'https://media.gcflearnfree.org/content/55e073de7dd48174331f51b3_01_17_2014/getting_started_interactive2.png',
44
+ ],
45
+ }),
46
+ ).pipe(Plugin.make)(),
47
+ },
48
+ };