@dxos/plugin-registry 0.8.4-main.8360d9e660 → 0.8.4-main.8baae0fced

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 (262) 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 -97
  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-MYLRMFLY.mjs +34 -0
  9. package/dist/lib/neutral/chunk-MYLRMFLY.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 +70 -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/{cli/commands → commands}/disable.d.ts +2 -2
  40. package/dist/types/src/commands/disable.d.ts.map +1 -0
  41. package/dist/types/src/{cli/commands → commands}/enable.d.ts +2 -2
  42. package/dist/types/src/commands/enable.d.ts.map +1 -0
  43. package/dist/types/src/commands/index.d.ts.map +1 -0
  44. package/dist/types/src/commands/list.d.ts +7 -0
  45. package/dist/types/src/commands/list.d.ts.map +1 -0
  46. package/dist/types/src/commands/util.d.ts.map +1 -0
  47. package/dist/types/src/components/DisableDependentsAlert/DisableDependentsAlert.d.ts +24 -0
  48. package/dist/types/src/components/DisableDependentsAlert/DisableDependentsAlert.d.ts.map +1 -0
  49. package/dist/types/src/components/DisableDependentsAlert/index.d.ts +2 -0
  50. package/dist/types/src/components/DisableDependentsAlert/index.d.ts.map +1 -0
  51. package/dist/types/src/components/PluginDetail/PluginDetail.d.ts +75 -2
  52. package/dist/types/src/components/PluginDetail/PluginDetail.d.ts.map +1 -1
  53. package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts +60 -16
  54. package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts.map +1 -1
  55. package/dist/types/src/components/PluginFailureBadge/PluginFailureBadge.d.ts +20 -0
  56. package/dist/types/src/components/PluginFailureBadge/PluginFailureBadge.d.ts.map +1 -0
  57. package/dist/types/src/components/PluginFailureBadge/index.d.ts +2 -0
  58. package/dist/types/src/components/PluginFailureBadge/index.d.ts.map +1 -0
  59. package/dist/types/src/components/PluginList/PluginItem.d.ts +29 -2
  60. package/dist/types/src/components/PluginList/PluginItem.d.ts.map +1 -1
  61. package/dist/types/src/components/PluginList/PluginList.d.ts +18 -3
  62. package/dist/types/src/components/PluginList/PluginList.d.ts.map +1 -1
  63. package/dist/types/src/components/PluginList/PluginList.stories.d.ts +58 -16
  64. package/dist/types/src/components/PluginList/PluginList.stories.d.ts.map +1 -1
  65. package/dist/types/src/components/RegistrySettings/RegistrySettings.d.ts +18 -0
  66. package/dist/types/src/components/RegistrySettings/RegistrySettings.d.ts.map +1 -0
  67. package/dist/types/src/components/RegistrySettings/index.d.ts +2 -0
  68. package/dist/types/src/components/RegistrySettings/index.d.ts.map +1 -0
  69. package/dist/types/src/components/index.d.ts +3 -0
  70. package/dist/types/src/components/index.d.ts.map +1 -1
  71. package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts +20 -0
  72. package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts.map +1 -0
  73. package/dist/types/src/containers/BaseRegistryArticle/index.d.ts +2 -0
  74. package/dist/types/src/containers/BaseRegistryArticle/index.d.ts.map +1 -0
  75. package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts +4 -0
  76. package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts.map +1 -0
  77. package/dist/types/src/containers/LoadPluginDialog/index.d.ts +2 -0
  78. package/dist/types/src/containers/LoadPluginDialog/index.d.ts.map +1 -0
  79. package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts.map +1 -1
  80. package/dist/types/src/containers/PluginArticle/index.d.ts +1 -2
  81. package/dist/types/src/containers/PluginArticle/index.d.ts.map +1 -1
  82. package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts +8 -0
  83. package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts.map +1 -0
  84. package/dist/types/src/containers/PublicRegistryArticle/index.d.ts +2 -0
  85. package/dist/types/src/containers/PublicRegistryArticle/index.d.ts.map +1 -0
  86. package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts +14 -0
  87. package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts.map +1 -0
  88. package/dist/types/src/containers/RegistryArticle/index.d.ts +2 -0
  89. package/dist/types/src/containers/RegistryArticle/index.d.ts.map +1 -0
  90. package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts +13 -0
  91. package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts.map +1 -0
  92. package/dist/types/src/containers/RegistrySettingsContainer/index.d.ts +2 -0
  93. package/dist/types/src/containers/RegistrySettingsContainer/index.d.ts.map +1 -0
  94. package/dist/types/src/containers/index.d.ts +5 -1
  95. package/dist/types/src/containers/index.d.ts.map +1 -1
  96. package/dist/types/src/hooks/index.d.ts +9 -0
  97. package/dist/types/src/hooks/index.d.ts.map +1 -0
  98. package/dist/types/src/hooks/useAutoTags.d.ts +20 -0
  99. package/dist/types/src/hooks/useAutoTags.d.ts.map +1 -0
  100. package/dist/types/src/hooks/useCatalogEntry.d.ts +11 -0
  101. package/dist/types/src/hooks/useCatalogEntry.d.ts.map +1 -0
  102. package/dist/types/src/hooks/useDisableConfirmation.d.ts +19 -0
  103. package/dist/types/src/hooks/useDisableConfirmation.d.ts.map +1 -0
  104. package/dist/types/src/hooks/useInstalledVersionTag.d.ts +18 -0
  105. package/dist/types/src/hooks/useInstalledVersionTag.d.ts.map +1 -0
  106. package/dist/types/src/hooks/usePluginActions.d.ts +36 -0
  107. package/dist/types/src/hooks/usePluginActions.d.ts.map +1 -0
  108. package/dist/types/src/hooks/useRegistryPlugins.d.ts +15 -0
  109. package/dist/types/src/hooks/useRegistryPlugins.d.ts.map +1 -0
  110. package/dist/types/src/hooks/useUpdateAvailable.d.ts +18 -0
  111. package/dist/types/src/hooks/useUpdateAvailable.d.ts.map +1 -0
  112. package/dist/types/src/hooks/useVersionPicker.d.ts +21 -0
  113. package/dist/types/src/hooks/useVersionPicker.d.ts.map +1 -0
  114. package/dist/types/src/index.d.ts +1 -1
  115. package/dist/types/src/index.d.ts.map +1 -1
  116. package/dist/types/src/meta.d.ts +16 -1
  117. package/dist/types/src/meta.d.ts.map +1 -1
  118. package/dist/types/src/operations/index.d.ts +3 -0
  119. package/dist/types/src/operations/index.d.ts.map +1 -0
  120. package/dist/types/src/operations/open-plugin-registry.d.ts +5 -0
  121. package/dist/types/src/operations/open-plugin-registry.d.ts.map +1 -0
  122. package/dist/types/src/plugin.d.ts +4 -0
  123. package/dist/types/src/plugin.d.ts.map +1 -0
  124. package/dist/types/src/storage.d.ts.map +1 -0
  125. package/dist/types/src/translations.d.ts +57 -16
  126. package/dist/types/src/translations.d.ts.map +1 -1
  127. package/dist/types/src/types.d.ts +30 -1
  128. package/dist/types/src/types.d.ts.map +1 -1
  129. package/dist/types/tsconfig.tsbuildinfo +1 -1
  130. package/package.json +60 -37
  131. package/src/{cli/plugin.ts → RegistryPlugin.node.ts} +3 -1
  132. package/src/RegistryPlugin.test.ts +24 -0
  133. package/src/RegistryPlugin.tsx +13 -5
  134. package/src/RegistryPlugin.workerd.ts +11 -0
  135. package/src/capabilities/app-graph-builder.ts +200 -0
  136. package/src/capabilities/dev-plugin-loader.ts +39 -0
  137. package/src/capabilities/index.ts +11 -3
  138. package/src/capabilities/operation-handler.ts +16 -0
  139. package/src/capabilities/react-surface.tsx +145 -0
  140. package/src/capabilities/settings.ts +34 -0
  141. package/src/components/DisableDependentsAlert/DisableDependentsAlert.tsx +69 -0
  142. package/src/components/DisableDependentsAlert/index.ts +5 -0
  143. package/src/components/PluginDetail/PluginDetail.stories.tsx +5 -4
  144. package/src/components/PluginDetail/PluginDetail.tsx +320 -62
  145. package/src/components/PluginFailureBadge/PluginFailureBadge.tsx +68 -0
  146. package/src/components/PluginFailureBadge/index.ts +5 -0
  147. package/src/components/PluginList/PluginItem.tsx +117 -15
  148. package/src/components/PluginList/PluginList.stories.tsx +17 -14
  149. package/src/components/PluginList/PluginList.tsx +32 -4
  150. package/src/components/RegistrySettings/RegistrySettings.tsx +120 -0
  151. package/src/components/RegistrySettings/index.ts +5 -0
  152. package/src/components/index.ts +3 -0
  153. package/src/containers/BaseRegistryArticle/BaseRegistryArticle.tsx +178 -0
  154. package/src/containers/BaseRegistryArticle/index.ts +5 -0
  155. package/src/containers/LoadPluginDialog/LoadPluginDialog.tsx +87 -0
  156. package/src/containers/LoadPluginDialog/index.ts +5 -0
  157. package/src/containers/PluginArticle/PluginArticle.tsx +102 -13
  158. package/src/containers/PluginArticle/index.ts +1 -3
  159. package/src/containers/PublicRegistryArticle/PublicRegistryArticle.tsx +192 -0
  160. package/src/containers/PublicRegistryArticle/index.ts +5 -0
  161. package/src/containers/RegistryArticle/RegistryArticle.tsx +53 -0
  162. package/src/containers/RegistryArticle/index.ts +5 -0
  163. package/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.tsx +59 -0
  164. package/src/containers/RegistrySettingsContainer/index.ts +5 -0
  165. package/src/containers/index.ts +5 -1
  166. package/src/hooks/index.ts +12 -0
  167. package/src/hooks/useAutoTags.ts +51 -0
  168. package/src/hooks/useCatalogEntry.ts +25 -0
  169. package/src/hooks/useDisableConfirmation.ts +58 -0
  170. package/src/hooks/useInstalledVersionTag.ts +41 -0
  171. package/src/hooks/usePluginActions.ts +174 -0
  172. package/src/hooks/useRegistryPlugins.ts +29 -0
  173. package/src/hooks/useUpdateAvailable.ts +31 -0
  174. package/src/hooks/useVersionPicker.ts +112 -0
  175. package/src/index.ts +1 -1
  176. package/src/meta.ts +26 -1
  177. package/src/operations/index.ts +7 -0
  178. package/src/operations/open-plugin-registry.ts +21 -0
  179. package/src/plugin.ts +11 -0
  180. package/src/translations.ts +62 -15
  181. package/src/types.ts +33 -1
  182. package/dist/lib/browser/PluginArticle-N2ITBUGD.mjs +0 -29
  183. package/dist/lib/browser/PluginArticle-N2ITBUGD.mjs.map +0 -7
  184. package/dist/lib/browser/PluginRegistry-DKO4UEYH.mjs +0 -78
  185. package/dist/lib/browser/PluginRegistry-DKO4UEYH.mjs.map +0 -7
  186. package/dist/lib/browser/app-graph-builder-AHS5BI4Y.mjs +0 -167
  187. package/dist/lib/browser/app-graph-builder-AHS5BI4Y.mjs.map +0 -7
  188. package/dist/lib/browser/chunk-2YWVCU2W.mjs +0 -191
  189. package/dist/lib/browser/chunk-2YWVCU2W.mjs.map +0 -7
  190. package/dist/lib/browser/chunk-KMUDYITM.mjs +0 -21
  191. package/dist/lib/browser/chunk-KMUDYITM.mjs.map +0 -7
  192. package/dist/lib/browser/cli/index.mjs.map +0 -7
  193. package/dist/lib/browser/index.mjs +0 -65
  194. package/dist/lib/browser/index.mjs.map +0 -7
  195. package/dist/lib/browser/meta.json +0 -1
  196. package/dist/lib/browser/operation-resolver-PK4B2BZQ.mjs +0 -25
  197. package/dist/lib/browser/operation-resolver-PK4B2BZQ.mjs.map +0 -7
  198. package/dist/lib/browser/react-surface-ZBP7YXKO.mjs +0 -85
  199. package/dist/lib/browser/react-surface-ZBP7YXKO.mjs.map +0 -7
  200. package/dist/lib/node-esm/PluginArticle-3NEYDKN5.mjs +0 -30
  201. package/dist/lib/node-esm/PluginArticle-3NEYDKN5.mjs.map +0 -7
  202. package/dist/lib/node-esm/PluginRegistry-BYKWC732.mjs +0 -79
  203. package/dist/lib/node-esm/PluginRegistry-BYKWC732.mjs.map +0 -7
  204. package/dist/lib/node-esm/app-graph-builder-DBDL5WWF.mjs +0 -168
  205. package/dist/lib/node-esm/app-graph-builder-DBDL5WWF.mjs.map +0 -7
  206. package/dist/lib/node-esm/chunk-BOCFM775.mjs +0 -192
  207. package/dist/lib/node-esm/chunk-BOCFM775.mjs.map +0 -7
  208. package/dist/lib/node-esm/chunk-ENSNUFQK.mjs +0 -23
  209. package/dist/lib/node-esm/chunk-ENSNUFQK.mjs.map +0 -7
  210. package/dist/lib/node-esm/cli/index.mjs +0 -223
  211. package/dist/lib/node-esm/cli/index.mjs.map +0 -7
  212. package/dist/lib/node-esm/index.mjs +0 -66
  213. package/dist/lib/node-esm/index.mjs.map +0 -7
  214. package/dist/lib/node-esm/meta.json +0 -1
  215. package/dist/lib/node-esm/operation-resolver-CCHN4NBP.mjs +0 -26
  216. package/dist/lib/node-esm/operation-resolver-CCHN4NBP.mjs.map +0 -7
  217. package/dist/lib/node-esm/react-surface-FJHMLGZF.mjs +0 -86
  218. package/dist/lib/node-esm/react-surface-FJHMLGZF.mjs.map +0 -7
  219. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts.map +0 -1
  220. package/dist/types/src/capabilities/app-graph-builder/index.d.ts +0 -3
  221. package/dist/types/src/capabilities/app-graph-builder/index.d.ts.map +0 -1
  222. package/dist/types/src/capabilities/operation-resolver/index.d.ts +0 -3
  223. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +0 -1
  224. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +0 -5
  225. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +0 -1
  226. package/dist/types/src/capabilities/react-surface/index.d.ts +0 -3
  227. package/dist/types/src/capabilities/react-surface/index.d.ts.map +0 -1
  228. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +0 -1
  229. package/dist/types/src/cli/commands/disable.d.ts.map +0 -1
  230. package/dist/types/src/cli/commands/enable.d.ts.map +0 -1
  231. package/dist/types/src/cli/commands/index.d.ts.map +0 -1
  232. package/dist/types/src/cli/commands/list.d.ts +0 -7
  233. package/dist/types/src/cli/commands/list.d.ts.map +0 -1
  234. package/dist/types/src/cli/commands/util.d.ts.map +0 -1
  235. package/dist/types/src/cli/index.d.ts +0 -3
  236. package/dist/types/src/cli/index.d.ts.map +0 -1
  237. package/dist/types/src/cli/plugin.d.ts.map +0 -1
  238. package/dist/types/src/cli/storage.d.ts.map +0 -1
  239. package/dist/types/src/containers/PluginRegistry/PluginRegistry.d.ts +0 -8
  240. package/dist/types/src/containers/PluginRegistry/PluginRegistry.d.ts.map +0 -1
  241. package/dist/types/src/containers/PluginRegistry/index.d.ts +0 -3
  242. package/dist/types/src/containers/PluginRegistry/index.d.ts.map +0 -1
  243. package/src/capabilities/app-graph-builder/app-graph-builder.ts +0 -137
  244. package/src/capabilities/app-graph-builder/index.ts +0 -7
  245. package/src/capabilities/operation-resolver/index.ts +0 -7
  246. package/src/capabilities/operation-resolver/operation-resolver.ts +0 -24
  247. package/src/capabilities/react-surface/index.ts +0 -7
  248. package/src/capabilities/react-surface/react-surface.tsx +0 -88
  249. package/src/cli/index.ts +0 -6
  250. package/src/containers/PluginRegistry/PluginRegistry.tsx +0 -86
  251. package/src/containers/PluginRegistry/index.ts +0 -7
  252. /package/dist/types/src/capabilities/{app-graph-builder/app-graph-builder.d.ts → app-graph-builder.d.ts} +0 -0
  253. /package/dist/types/src/capabilities/{react-surface/react-surface.d.ts → react-surface.d.ts} +0 -0
  254. /package/dist/types/src/{cli/commands → commands}/index.d.ts +0 -0
  255. /package/dist/types/src/{cli/commands → commands}/util.d.ts +0 -0
  256. /package/dist/types/src/{cli/storage.d.ts → storage.d.ts} +0 -0
  257. /package/src/{cli/commands → commands}/disable.ts +0 -0
  258. /package/src/{cli/commands → commands}/enable.ts +0 -0
  259. /package/src/{cli/commands → commands}/index.ts +0 -0
  260. /package/src/{cli/commands → commands}/list.ts +0 -0
  261. /package/src/{cli/commands → commands}/util.ts +0 -0
  262. /package/src/{cli/storage.ts → storage.ts} +0 -0
@@ -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,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';
@@ -5,10 +5,10 @@
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
 
7
7
  import { Plugin } from '@dxos/app-framework';
8
- import { faker } from '@dxos/random';
8
+ import { random } from '@dxos/random';
9
9
  import { withLayout, withTheme } from '@dxos/react-ui/testing';
10
10
 
11
- import { translations } from '../../translations';
11
+ import { translations } from '#translations';
12
12
 
13
13
  import { PluginDetail } from './PluginDetail';
14
14
 
@@ -29,9 +29,10 @@ type Story = StoryObj<typeof meta>;
29
29
  export const Default: Story = {
30
30
  args: {
31
31
  plugin: Plugin.define({
32
- id: 'example.com/plugin/test-plugin',
32
+ id: 'com.example.plugin.test',
33
33
  name: 'Test Plugin',
34
- description: faker.lorem.paragraphs(2),
34
+ author: 'DXOS',
35
+ description: random.lorem.paragraphs(2),
35
36
  icon: 'ph--bug--regular',
36
37
  iconHue: 'sky',
37
38
  homePage: 'https://example.com',
@@ -2,80 +2,338 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import React from 'react';
5
+ import React, { type PropsWithChildren } from 'react';
6
6
 
7
- import { type Plugin } from '@dxos/app-framework';
8
- import { Icon, Input, Link, useTranslation } from '@dxos/react-ui';
9
- import { ScrollArea } from '@dxos/react-ui';
10
- import { getStyles, mx } from '@dxos/ui-theme';
7
+ import { type Plugin, type PluginManager, type Registry } from '@dxos/app-framework';
8
+ import {
9
+ Button,
10
+ Carousel,
11
+ Icon,
12
+ Input,
13
+ Link,
14
+ ScrollArea,
15
+ Select,
16
+ Tag,
17
+ ThemedClassName,
18
+ useTranslation,
19
+ } from '@dxos/react-ui';
20
+ import { composable, composableProps, getStyles, mx } from '@dxos/ui-theme';
11
21
 
12
- import { meta } from '../../meta';
22
+ import { meta } from '#meta';
23
+
24
+ import { PluginFailureBadge } from '../PluginFailureBadge';
13
25
 
14
26
  export type PluginDetailProps = {
15
27
  plugin: Plugin.Plugin;
16
28
  enabled?: boolean;
29
+ /** True while an in-flight install is running. Disables the install button. */
30
+ installing?: boolean;
31
+ /** True while an in-flight update is running. Disables the update button. */
32
+ updating?: boolean;
33
+ /** True when the catalog has a newer version than the one installed. */
34
+ hasUpdate?: boolean;
35
+ /** Currently installed version tag (used to mark the matching select option). */
36
+ installedVersionTag?: string;
37
+ /** Currently selected version tag in the picker. */
38
+ selectedVersionTag?: string;
39
+ /** Available versions of this plugin from the catalog. When non-empty, a version picker is shown. */
40
+ versions?: readonly Registry.PluginVersion[];
41
+ /**
42
+ * Ids of plugins this plugin declares as dependencies (direct only). Rendered
43
+ * under a "Requires" heading so the user can see what enabling this plugin
44
+ * will auto-enable.
45
+ */
46
+ dependencies?: readonly string[];
47
+ /**
48
+ * Ids of plugins that declare this plugin as a dependency (direct only).
49
+ * Rendered under a "Required by" heading so the user understands the
50
+ * downstream impact of disabling this plugin.
51
+ */
52
+ dependents?: readonly string[];
53
+ /**
54
+ * Failure record for this plugin, if any. When present a warning badge is
55
+ * rendered next to the plugin name in the header.
56
+ */
57
+ failure?: PluginManager.PluginFailure;
17
58
  onEnabledChange?: (enabled: boolean) => void;
59
+ /**
60
+ * When provided, the plugin is not installed and an Install button is shown
61
+ * in place of the enable Switch.
62
+ */
63
+ onInstall?: () => void;
64
+ /**
65
+ * Called when the user clicks Install on the version picker.
66
+ */
67
+ onInstallVersion?: () => void;
68
+ /**
69
+ * Called when the user activates a dependency / dependent chip. When
70
+ * provided, chips are rendered as buttons that fire this handler with the
71
+ * target plugin id; when omitted, chips are non-interactive labels.
72
+ */
73
+ onNavigateToPlugin?: (pluginId: string) => void;
74
+ /**
75
+ * Called when the user activates the in-app spec viewer link. When provided
76
+ * (i.e. the plugin bundles its MDL via `Plugin.Meta.specContent`), a second
77
+ * "View specification" link is rendered next to the external spec link.
78
+ */
79
+ onOpenSpec?: () => void;
80
+ /**
81
+ * Resolves a plugin id to its display name for dependency / dependent
82
+ * chip labels. The component delegates the lookup to the parent so each
83
+ * surface can decide how to source names (e.g. `Plugin.Meta.name` from the
84
+ * registered plugin set). When omitted, chips render the raw id.
85
+ */
86
+ onResolvePluginName?: (pluginId: string) => string;
87
+ /**
88
+ * When provided, an Uninstall button is rendered. Leave undefined for core
89
+ * or non-removable plugins.
90
+ */
91
+ onUninstall?: () => void;
92
+ /**
93
+ * When provided and the plugin is installed with a newer catalog version available,
94
+ * an Update button is shown in place of the enable Switch.
95
+ */
96
+ onUpdate?: () => void;
97
+ /**
98
+ * Called when the user selects a different version in the picker.
99
+ */
100
+ onVersionChange?: (tag: string) => void;
18
101
  };
19
102
 
20
- export const PluginDetail = ({ plugin, enabled, onEnabledChange }: PluginDetailProps) => {
21
- const { t } = useTranslation(meta.id);
22
- const {
23
- id,
24
- name,
25
- description,
26
- homePage,
27
- source,
28
- screenshots,
29
- icon = 'ph--circle--regular',
30
- iconHue = 'neutral',
31
- } = plugin.meta;
32
- const styles = getStyles(iconHue);
33
-
34
- return (
35
- <ScrollArea.Root orientation='vertical'>
36
- <ScrollArea.Viewport>
37
- <div role='none' className='dx-document grid grid-cols-[min-content_1fr] gap-4 p-4'>
38
- <div role='none'>
39
- <Icon classNames={mx('p-1 rounded-md', styles.fill, styles.surfaceText)} icon={icon} size={14} />
40
- </div>
41
- <div role='none' className='flex flex-col gap-6'>
42
- <div role='none' className='grid grid-cols-[1fr_min-content] gap-x-3 w-full pt-1'>
43
- <h2 className='text-xl'>{name}</h2>
44
- <Input.Root>
45
- <Input.Switch classNames='self-center' checked={enabled} onCheckedChange={onEnabledChange} />
46
- </Input.Root>
47
- <p className='pt-0.5 text-sm text-description'>{id}</p>
48
- </div>
49
- <div role='none'>
50
- <p className='text-description'>{description}</p>
103
+ export const PluginDetail = composable<HTMLDivElement, PluginDetailProps>(
104
+ (
105
+ {
106
+ plugin,
107
+ enabled,
108
+ installing,
109
+ updating,
110
+ hasUpdate,
111
+ installedVersionTag,
112
+ selectedVersionTag,
113
+ versions,
114
+ dependencies,
115
+ dependents,
116
+ failure,
117
+ onEnabledChange,
118
+ onInstall,
119
+ onInstallVersion,
120
+ onNavigateToPlugin,
121
+ onOpenSpec,
122
+ onResolvePluginName,
123
+ onUninstall,
124
+ onUpdate,
125
+ onVersionChange,
126
+ ...props
127
+ },
128
+ forwardedRef,
129
+ ) => {
130
+ const { t } = useTranslation(meta.id);
131
+ const {
132
+ id,
133
+ name,
134
+ author,
135
+ description,
136
+ homePage,
137
+ source,
138
+ screenshots,
139
+ icon = 'ph--circle--regular',
140
+ iconHue = 'neutral',
141
+ } = plugin.meta;
142
+ const styles = getStyles(iconHue);
143
+
144
+ return (
145
+ <ScrollArea.Root {...composableProps(props)} orientation='vertical' ref={forwardedRef}>
146
+ <ScrollArea.Viewport>
147
+ {/*
148
+ * 3-column grid: [icon/prev | content/viewport | next].
149
+ * Most sections occupy `col-start-2 col-span-2` so they stretch from
150
+ * the centre column out to the trailing edge. The carousel uses
151
+ * `display: contents` to flatten its children into this grid so
152
+ * `Carousel.Previous` lands in col 1, `Carousel.Viewport` aligns
153
+ * with the rest of the content in col 2, and `Carousel.Next` sits
154
+ * in col 3.
155
+ */}
156
+ <div className='dx-document grid grid-cols-[4rem_minmax(0,1fr)_4rem] gap-x-4 p-4 items-start'>
157
+ <Icon classNames={mx('row-start-1 p-1 rounded-md', styles.fill, styles.foreground)} icon={icon} size={14} />
158
+
159
+ <div className='row-start-1 col-start-2 col-span-2 grid grid-cols-[1fr_min-content] gap-x-3 w-full pt-1'>
160
+ <div className='flex items-center gap-2'>
161
+ <h2 className='text-xl'>{name}</h2>
162
+ {failure && <PluginFailureBadge failure={failure} size={5} />}
163
+ </div>
164
+ {onInstall ? (
165
+ <Button density='fine' variant='primary' disabled={installing} onClick={onInstall}>
166
+ {installing ? t('installing.label') : t('install.label')}
167
+ </Button>
168
+ ) : (
169
+ <Input.Root>
170
+ <Input.Switch classNames='self-center' checked={enabled} onCheckedChange={onEnabledChange} />
171
+ </Input.Root>
172
+ )}
173
+ <div className='flex items-center gap-1 pt-0.5 text-sm text-description'>
174
+ {id}
175
+ {author && <span className='dx-tag dx-tag--info'>{author}</span>}
176
+ </div>
51
177
  </div>
178
+
179
+ <Section.Root>
180
+ <Section.Heading title='Description' />
181
+ <Section.Body>
182
+ <p className='text-description'>{description}</p>
183
+ </Section.Body>
184
+ </Section.Root>
185
+
52
186
  {screenshots && screenshots.length > 0 && (
53
- <div role='none' className='flex flex-col gap-2'>
54
- <h2>Preview</h2>
55
- <img src={screenshots[0]} alt={name} className='aspect-video object-fit' />
56
- </div>
187
+ <Section.Root>
188
+ <Section.Heading title='Preview' />
189
+ <Section.Body>
190
+ <Carousel.Root classNames='contents' count={screenshots.length}>
191
+ <Carousel.Viewport>
192
+ {screenshots.map((src, index) => (
193
+ <Carousel.Slide key={src} index={index} src={src} alt={name} />
194
+ ))}
195
+ </Carousel.Viewport>
196
+ <Carousel.Indicators />
197
+ </Carousel.Root>
198
+ </Section.Body>
199
+ </Section.Root>
200
+ )}
201
+
202
+ <Section.Root>
203
+ <Section.Heading title={t('resources.label')} />
204
+ <Section.Body>
205
+ <div className='flex gap-3 items-center'>
206
+ {homePage && (
207
+ <Link href={homePage} classNames='text-sm text-description'>
208
+ {t('home-page.label')}
209
+ <Icon icon='ph--arrow-square-out--regular' size={3} classNames='ml-1 dx-icon-inline' />
210
+ </Link>
211
+ )}
212
+
213
+ {source && (
214
+ <Link href={source} classNames='text-sm text-description'>
215
+ {t('source.label')}
216
+ <Icon icon='ph--arrow-square-out--regular' size={3} classNames='ml-1 dx-icon-inline' />
217
+ </Link>
218
+ )}
219
+
220
+ {onOpenSpec && <Chip id={id} name={t('open-spec.label')} onClick={onOpenSpec} />}
221
+ </div>
222
+ </Section.Body>
223
+ </Section.Root>
224
+
225
+ {dependencies && dependencies.length > 0 && (
226
+ <Section.Root>
227
+ <Section.Heading title={t('dependencies.label')} />
228
+ <Section.Body>
229
+ <div className='flex flex-wrap gap-1'>
230
+ {dependencies.map((depId) => (
231
+ <Chip
232
+ key={depId}
233
+ id={depId}
234
+ name={onResolvePluginName?.(depId) ?? depId}
235
+ onClick={onNavigateToPlugin}
236
+ />
237
+ ))}
238
+ </div>
239
+ </Section.Body>
240
+ </Section.Root>
241
+ )}
242
+
243
+ {dependents && dependents.length > 0 && (
244
+ <Section.Root>
245
+ <Section.Heading title={t('dependents.label')} />
246
+ <Section.Body>
247
+ <div className='flex flex-wrap gap-1'>
248
+ {dependents.map((dependentId) => (
249
+ <Chip
250
+ key={dependentId}
251
+ id={dependentId}
252
+ name={onResolvePluginName?.(dependentId) ?? dependentId}
253
+ onClick={onNavigateToPlugin}
254
+ />
255
+ ))}
256
+ </div>
257
+ </Section.Body>
258
+ </Section.Root>
57
259
  )}
58
- <div role='none' className='flex flex-col gap-2'>
59
- <h2>Resources</h2>
60
- <div className='flex gap-2'>
61
- {homePage && (
62
- <Link href={homePage} target='_blank' rel='noreferrer' classNames='text-sm text-description'>
63
- {t('home page label')}
64
- <Icon icon='ph--arrow-square-out--bold' size={3} classNames='inline-block leading-none mx-1' />
65
- </Link>
66
- )}
67
-
68
- {source && (
69
- <Link href={source} target='_blank' rel='noreferrer' classNames='text-sm text-description'>
70
- {t('source label')}
71
- <Icon icon='ph--arrow-square-out--bold' size={3} classNames='inline-block leading-none mx-1' />
72
- </Link>
73
- )}
260
+
261
+ {versions && versions.length > 0 && (
262
+ <Section.Root>
263
+ <Section.Heading title={t('versions.label')} />
264
+ <Section.Body>
265
+ <div className='flex gap-2 items-center'>
266
+ <Select.Root value={selectedVersionTag} onValueChange={onVersionChange}>
267
+ <Select.TriggerButton classNames='min-w-32' />
268
+ <Select.Portal>
269
+ <Select.Content>
270
+ <Select.Viewport>
271
+ {versions.map((versionEntry) => (
272
+ <Select.Option key={versionEntry.tag} value={versionEntry.tag}>
273
+ {versionEntry.tag}
274
+ {installedVersionTag === versionEntry.tag ? ` (${t('installed.label')})` : ''}
275
+ </Select.Option>
276
+ ))}
277
+ </Select.Viewport>
278
+ </Select.Content>
279
+ </Select.Portal>
280
+ </Select.Root>
281
+ {onInstallVersion && (
282
+ <Button
283
+ density='fine'
284
+ variant='primary'
285
+ disabled={installing || selectedVersionTag === installedVersionTag}
286
+ onClick={onInstallVersion}
287
+ >
288
+ {installing ? t('installing.label') : t('install-version.label')}
289
+ </Button>
290
+ )}
291
+ </div>
292
+ </Section.Body>
293
+ </Section.Root>
294
+ )}
295
+
296
+ {(onUninstall || (hasUpdate && onUpdate) || updating) && (
297
+ <div className='col-start-2 col-span-2 flex gap-2'>
298
+ {updating ? (
299
+ <Button variant='primary' disabled>
300
+ {t('updating.label')}
301
+ </Button>
302
+ ) : hasUpdate && onUpdate ? (
303
+ <Button variant='primary' onClick={onUpdate}>
304
+ {t('update.label')}
305
+ </Button>
306
+ ) : null}
307
+ {onUninstall && <Button onClick={onUninstall}>{t('uninstall.label')}</Button>}
74
308
  </div>
75
- </div>
309
+ )}
76
310
  </div>
77
- </div>
78
- </ScrollArea.Viewport>
79
- </ScrollArea.Root>
80
- );
311
+ </ScrollArea.Viewport>
312
+ </ScrollArea.Root>
313
+ );
314
+ },
315
+ );
316
+
317
+ PluginDetail.displayName = 'PluginDetail';
318
+
319
+ const SectionRoot = ({ children }: PropsWithChildren<{}>) => <>{children}</>;
320
+
321
+ const SectionHeading = ({ title }: { title: string }) => (
322
+ <h2 className='col-start-2 col-span-2 pt-6 pb-2 uppercase text-sm font-medium text-subdued'>{title}</h2>
323
+ );
324
+
325
+ const SectionBody = ({ classNames, children }: ThemedClassName<PropsWithChildren>) => (
326
+ <div className={mx('col-start-2 flex flex-col gap-2', classNames)}>{children}</div>
327
+ );
328
+
329
+ const Section = {
330
+ Root: SectionRoot,
331
+ Heading: SectionHeading,
332
+ Body: SectionBody,
81
333
  };
334
+
335
+ const Chip = ({ id, name, onClick }: { id: string; name: string; onClick?: (pluginId: string) => void }) => (
336
+ <Tag title={id} onClick={onClick ? () => onClick(id) : undefined} classNames='dx-hover'>
337
+ {name}
338
+ </Tag>
339
+ );
@@ -0,0 +1,68 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { type PluginManager } from '@dxos/app-framework';
8
+ import { IconButton, Popover, useTranslation } from '@dxos/react-ui';
9
+
10
+ import { meta } from '#meta';
11
+
12
+ // Mirrors react-ui `Icon`'s `size` prop literal subset; kept inline so we don't
13
+ // have to add `@dxos/ui-types` as a dep just for one type alias.
14
+ type IconSize = 4 | 5 | 6 | 8;
15
+
16
+ export type PluginFailureBadgeProps = {
17
+ failure: PluginManager.PluginFailure;
18
+ /** Optional override for the trigger icon's tailwind classes. */
19
+ iconClassNames?: string;
20
+ /** Visual size of the warning icon (passed through to react-ui `<Icon />`). */
21
+ size?: IconSize;
22
+ };
23
+
24
+ /**
25
+ * Compact warning glyph rendered next to a plugin's name when it has failed
26
+ * to load or activate. Clicking the icon opens a popover that names the phase
27
+ * (load / activation), the reason (timeout / error), and the underlying error
28
+ * message — enough for an operator to tell "remote host is offline" apart
29
+ * from "the plugin crashed".
30
+ */
31
+ export const PluginFailureBadge = ({ failure, iconClassNames, size = 4 }: PluginFailureBadgeProps) => {
32
+ const { t } = useTranslation(meta.id);
33
+
34
+ const phaseLabel = failure.phase === 'load' ? t('failure-phase-load.label') : t('failure-phase-activation.label');
35
+ const reasonLabel =
36
+ failure.reason === 'timeout' ? t('failure-reason-timeout.label') : t('failure-reason-error.label');
37
+
38
+ return (
39
+ <Popover.Root>
40
+ <Popover.Trigger asChild>
41
+ <IconButton
42
+ variant='ghost'
43
+ iconOnly
44
+ noTooltip
45
+ icon='ph--warning--bold'
46
+ size={size}
47
+ iconClassNames={iconClassNames}
48
+ label={t('failure-badge.label')}
49
+ data-testid={`pluginFailureBadge.${failure.id}`}
50
+ // Don't bubble up to row click handlers (e.g. the card's `onClick`
51
+ // that opens the details pane). The popover is the action here.
52
+ onClick={(event) => event.stopPropagation()}
53
+ />
54
+ </Popover.Trigger>
55
+ <Popover.Content>
56
+ <Popover.Viewport>
57
+ <div className='px-3 py-2 min-w-[18rem] max-w-[28rem] flex flex-col gap-1'>
58
+ <p className='font-medium text-sm'>
59
+ {t('failure-title.label', { phase: phaseLabel, reason: reasonLabel })}
60
+ </p>
61
+ <p className='text-description text-sm break-words'>{failure.error.message}</p>
62
+ </div>
63
+ </Popover.Viewport>
64
+ <Popover.Arrow />
65
+ </Popover.Content>
66
+ </Popover.Root>
67
+ );
68
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './PluginFailureBadge';