@dxos/plugin-registry 0.8.4-main.d05673bc65 → 0.8.4-main.dfabb4ec29

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 (234) hide show
  1. package/dist/lib/neutral/RegistryPlugin.mjs +27 -0
  2. package/dist/lib/neutral/RegistryPlugin.mjs.map +7 -0
  3. package/dist/lib/{browser/cli/index.mjs → neutral/RegistryPlugin.node.mjs} +28 -97
  4. package/dist/lib/neutral/RegistryPlugin.node.mjs.map +7 -0
  5. package/dist/lib/neutral/chunk-XK2JMH3H.mjs +33 -0
  6. package/dist/lib/neutral/chunk-XK2JMH3H.mjs.map +7 -0
  7. package/dist/lib/{browser/chunk-KMUDYITM.mjs → neutral/chunk-YV7A523U.mjs} +11 -3
  8. package/dist/lib/neutral/chunk-YV7A523U.mjs.map +7 -0
  9. package/dist/lib/neutral/index.mjs +21 -0
  10. package/dist/lib/neutral/index.mjs.map +7 -0
  11. package/dist/lib/neutral/meta.json +1 -0
  12. package/dist/lib/neutral/open-plugin-registry-II735N76.mjs +15 -0
  13. package/dist/lib/neutral/open-plugin-registry-II735N76.mjs.map +7 -0
  14. package/dist/lib/neutral/plugin.mjs +18 -0
  15. package/dist/lib/neutral/plugin.mjs.map +7 -0
  16. package/dist/lib/neutral/translations.mjs +61 -0
  17. package/dist/lib/neutral/translations.mjs.map +7 -0
  18. package/dist/types/src/RegistryPlugin.d.ts +1 -0
  19. package/dist/types/src/RegistryPlugin.d.ts.map +1 -1
  20. package/dist/types/src/{cli/plugin.d.ts → RegistryPlugin.node.d.ts} +2 -1
  21. package/dist/types/src/RegistryPlugin.node.d.ts.map +1 -0
  22. package/dist/types/src/RegistryPlugin.test.d.ts +2 -0
  23. package/dist/types/src/RegistryPlugin.test.d.ts.map +1 -0
  24. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  25. package/dist/types/src/capabilities/dev-plugin-loader.d.ts +11 -0
  26. package/dist/types/src/capabilities/dev-plugin-loader.d.ts.map +1 -0
  27. package/dist/types/src/capabilities/index.d.ts +15 -3
  28. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  29. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  30. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  31. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  32. package/dist/types/src/capabilities/settings.d.ts +14 -0
  33. package/dist/types/src/capabilities/settings.d.ts.map +1 -0
  34. package/dist/types/src/{cli/commands → commands}/disable.d.ts +2 -2
  35. package/dist/types/src/commands/disable.d.ts.map +1 -0
  36. package/dist/types/src/{cli/commands → commands}/enable.d.ts +2 -2
  37. package/dist/types/src/commands/enable.d.ts.map +1 -0
  38. package/dist/types/src/commands/index.d.ts.map +1 -0
  39. package/dist/types/src/commands/list.d.ts +7 -0
  40. package/dist/types/src/commands/list.d.ts.map +1 -0
  41. package/dist/types/src/commands/util.d.ts.map +1 -0
  42. package/dist/types/src/components/PluginDetail/PluginDetail.d.ts +39 -2
  43. package/dist/types/src/components/PluginDetail/PluginDetail.d.ts.map +1 -1
  44. package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts +51 -16
  45. package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts.map +1 -1
  46. package/dist/types/src/components/PluginFailureBadge/PluginFailureBadge.d.ts +20 -0
  47. package/dist/types/src/components/PluginFailureBadge/PluginFailureBadge.d.ts.map +1 -0
  48. package/dist/types/src/components/PluginFailureBadge/index.d.ts +2 -0
  49. package/dist/types/src/components/PluginFailureBadge/index.d.ts.map +1 -0
  50. package/dist/types/src/components/PluginList/PluginItem.d.ts +29 -2
  51. package/dist/types/src/components/PluginList/PluginItem.d.ts.map +1 -1
  52. package/dist/types/src/components/PluginList/PluginList.d.ts +18 -3
  53. package/dist/types/src/components/PluginList/PluginList.d.ts.map +1 -1
  54. package/dist/types/src/components/PluginList/PluginList.stories.d.ts +49 -16
  55. package/dist/types/src/components/PluginList/PluginList.stories.d.ts.map +1 -1
  56. package/dist/types/src/components/RegistrySettings/RegistrySettings.d.ts +18 -0
  57. package/dist/types/src/components/RegistrySettings/RegistrySettings.d.ts.map +1 -0
  58. package/dist/types/src/components/RegistrySettings/index.d.ts +2 -0
  59. package/dist/types/src/components/RegistrySettings/index.d.ts.map +1 -0
  60. package/dist/types/src/components/index.d.ts +2 -0
  61. package/dist/types/src/components/index.d.ts.map +1 -1
  62. package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts +20 -0
  63. package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts.map +1 -0
  64. package/dist/types/src/containers/BaseRegistryArticle/index.d.ts +2 -0
  65. package/dist/types/src/containers/BaseRegistryArticle/index.d.ts.map +1 -0
  66. package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts +4 -0
  67. package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts.map +1 -0
  68. package/dist/types/src/containers/LoadPluginDialog/index.d.ts +2 -0
  69. package/dist/types/src/containers/LoadPluginDialog/index.d.ts.map +1 -0
  70. package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts.map +1 -1
  71. package/dist/types/src/containers/PluginArticle/index.d.ts +1 -2
  72. package/dist/types/src/containers/PluginArticle/index.d.ts.map +1 -1
  73. package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts +8 -0
  74. package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts.map +1 -0
  75. package/dist/types/src/containers/PublicRegistryArticle/index.d.ts +2 -0
  76. package/dist/types/src/containers/PublicRegistryArticle/index.d.ts.map +1 -0
  77. package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts +14 -0
  78. package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts.map +1 -0
  79. package/dist/types/src/containers/RegistryArticle/index.d.ts +2 -0
  80. package/dist/types/src/containers/RegistryArticle/index.d.ts.map +1 -0
  81. package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts +13 -0
  82. package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts.map +1 -0
  83. package/dist/types/src/containers/RegistrySettingsContainer/index.d.ts +2 -0
  84. package/dist/types/src/containers/RegistrySettingsContainer/index.d.ts.map +1 -0
  85. package/dist/types/src/containers/index.d.ts +5 -1
  86. package/dist/types/src/containers/index.d.ts.map +1 -1
  87. package/dist/types/src/hooks/index.d.ts +4 -0
  88. package/dist/types/src/hooks/index.d.ts.map +1 -0
  89. package/dist/types/src/hooks/use-auto-tags.d.ts +20 -0
  90. package/dist/types/src/hooks/use-auto-tags.d.ts.map +1 -0
  91. package/dist/types/src/hooks/use-registry-plugins.d.ts +15 -0
  92. package/dist/types/src/hooks/use-registry-plugins.d.ts.map +1 -0
  93. package/dist/types/src/hooks/use-update-available.d.ts +18 -0
  94. package/dist/types/src/hooks/use-update-available.d.ts.map +1 -0
  95. package/dist/types/src/index.d.ts +1 -1
  96. package/dist/types/src/index.d.ts.map +1 -1
  97. package/dist/types/src/meta.d.ts +3 -1
  98. package/dist/types/src/meta.d.ts.map +1 -1
  99. package/dist/types/src/operations/index.d.ts +3 -0
  100. package/dist/types/src/operations/index.d.ts.map +1 -0
  101. package/dist/types/src/operations/open-plugin-registry.d.ts +5 -0
  102. package/dist/types/src/operations/open-plugin-registry.d.ts.map +1 -0
  103. package/dist/types/src/plugin.d.ts +4 -0
  104. package/dist/types/src/plugin.d.ts.map +1 -0
  105. package/dist/types/src/storage.d.ts.map +1 -0
  106. package/dist/types/src/translations.d.ts +48 -16
  107. package/dist/types/src/translations.d.ts.map +1 -1
  108. package/dist/types/src/types.d.ts +30 -1
  109. package/dist/types/src/types.d.ts.map +1 -1
  110. package/dist/types/tsconfig.tsbuildinfo +1 -1
  111. package/package.json +57 -36
  112. package/src/{cli/plugin.ts → RegistryPlugin.node.ts} +3 -1
  113. package/src/RegistryPlugin.test.ts +24 -0
  114. package/src/RegistryPlugin.tsx +13 -5
  115. package/src/capabilities/app-graph-builder.ts +200 -0
  116. package/src/capabilities/dev-plugin-loader.ts +39 -0
  117. package/src/capabilities/index.ts +11 -3
  118. package/src/capabilities/operation-handler.ts +16 -0
  119. package/src/capabilities/react-surface.tsx +136 -0
  120. package/src/capabilities/settings.ts +34 -0
  121. package/src/components/PluginDetail/PluginDetail.stories.tsx +4 -4
  122. package/src/components/PluginDetail/PluginDetail.tsx +175 -60
  123. package/src/components/PluginFailureBadge/PluginFailureBadge.tsx +68 -0
  124. package/src/components/PluginFailureBadge/index.ts +5 -0
  125. package/src/components/PluginList/PluginItem.tsx +116 -14
  126. package/src/components/PluginList/PluginList.stories.tsx +17 -14
  127. package/src/components/PluginList/PluginList.tsx +32 -4
  128. package/src/components/RegistrySettings/RegistrySettings.tsx +120 -0
  129. package/src/components/RegistrySettings/index.ts +5 -0
  130. package/src/components/index.ts +2 -0
  131. package/src/containers/BaseRegistryArticle/BaseRegistryArticle.tsx +163 -0
  132. package/src/containers/BaseRegistryArticle/index.ts +5 -0
  133. package/src/containers/LoadPluginDialog/LoadPluginDialog.tsx +87 -0
  134. package/src/containers/LoadPluginDialog/index.ts +5 -0
  135. package/src/containers/PluginArticle/PluginArticle.tsx +317 -9
  136. package/src/containers/PluginArticle/index.ts +1 -3
  137. package/src/containers/PublicRegistryArticle/PublicRegistryArticle.tsx +192 -0
  138. package/src/containers/PublicRegistryArticle/index.ts +5 -0
  139. package/src/containers/RegistryArticle/RegistryArticle.tsx +53 -0
  140. package/src/containers/RegistryArticle/index.ts +5 -0
  141. package/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.tsx +59 -0
  142. package/src/containers/RegistrySettingsContainer/index.ts +5 -0
  143. package/src/containers/index.ts +5 -1
  144. package/src/hooks/index.ts +7 -0
  145. package/src/hooks/use-auto-tags.ts +51 -0
  146. package/src/hooks/use-registry-plugins.ts +29 -0
  147. package/src/hooks/use-update-available.ts +31 -0
  148. package/src/index.ts +1 -1
  149. package/src/meta.ts +10 -1
  150. package/src/operations/index.ts +7 -0
  151. package/src/operations/open-plugin-registry.ts +21 -0
  152. package/src/plugin.ts +11 -0
  153. package/src/translations.ts +52 -15
  154. package/src/types.ts +33 -1
  155. package/dist/lib/browser/PluginArticle-74DKRXJH.mjs +0 -29
  156. package/dist/lib/browser/PluginArticle-74DKRXJH.mjs.map +0 -7
  157. package/dist/lib/browser/PluginRegistry-DORQQ7GM.mjs +0 -78
  158. package/dist/lib/browser/PluginRegistry-DORQQ7GM.mjs.map +0 -7
  159. package/dist/lib/browser/app-graph-builder-AHS5BI4Y.mjs +0 -167
  160. package/dist/lib/browser/app-graph-builder-AHS5BI4Y.mjs.map +0 -7
  161. package/dist/lib/browser/chunk-KMUDYITM.mjs.map +0 -7
  162. package/dist/lib/browser/chunk-QQUG6FNW.mjs +0 -191
  163. package/dist/lib/browser/chunk-QQUG6FNW.mjs.map +0 -7
  164. package/dist/lib/browser/cli/index.mjs.map +0 -7
  165. package/dist/lib/browser/index.mjs +0 -65
  166. package/dist/lib/browser/index.mjs.map +0 -7
  167. package/dist/lib/browser/meta.json +0 -1
  168. package/dist/lib/browser/operation-resolver-PK4B2BZQ.mjs +0 -25
  169. package/dist/lib/browser/operation-resolver-PK4B2BZQ.mjs.map +0 -7
  170. package/dist/lib/browser/react-surface-HPLHM3SK.mjs +0 -85
  171. package/dist/lib/browser/react-surface-HPLHM3SK.mjs.map +0 -7
  172. package/dist/lib/node-esm/PluginArticle-JPVC7NXQ.mjs +0 -30
  173. package/dist/lib/node-esm/PluginArticle-JPVC7NXQ.mjs.map +0 -7
  174. package/dist/lib/node-esm/PluginRegistry-3ZB4DFF7.mjs +0 -79
  175. package/dist/lib/node-esm/PluginRegistry-3ZB4DFF7.mjs.map +0 -7
  176. package/dist/lib/node-esm/app-graph-builder-DBDL5WWF.mjs +0 -168
  177. package/dist/lib/node-esm/app-graph-builder-DBDL5WWF.mjs.map +0 -7
  178. package/dist/lib/node-esm/chunk-ENSNUFQK.mjs +0 -23
  179. package/dist/lib/node-esm/chunk-ENSNUFQK.mjs.map +0 -7
  180. package/dist/lib/node-esm/chunk-II5WBDYI.mjs +0 -192
  181. package/dist/lib/node-esm/chunk-II5WBDYI.mjs.map +0 -7
  182. package/dist/lib/node-esm/cli/index.mjs +0 -223
  183. package/dist/lib/node-esm/cli/index.mjs.map +0 -7
  184. package/dist/lib/node-esm/index.mjs +0 -66
  185. package/dist/lib/node-esm/index.mjs.map +0 -7
  186. package/dist/lib/node-esm/meta.json +0 -1
  187. package/dist/lib/node-esm/operation-resolver-CCHN4NBP.mjs +0 -26
  188. package/dist/lib/node-esm/operation-resolver-CCHN4NBP.mjs.map +0 -7
  189. package/dist/lib/node-esm/react-surface-GM64KKI3.mjs +0 -86
  190. package/dist/lib/node-esm/react-surface-GM64KKI3.mjs.map +0 -7
  191. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts.map +0 -1
  192. package/dist/types/src/capabilities/app-graph-builder/index.d.ts +0 -3
  193. package/dist/types/src/capabilities/app-graph-builder/index.d.ts.map +0 -1
  194. package/dist/types/src/capabilities/operation-resolver/index.d.ts +0 -3
  195. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +0 -1
  196. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +0 -5
  197. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +0 -1
  198. package/dist/types/src/capabilities/react-surface/index.d.ts +0 -3
  199. package/dist/types/src/capabilities/react-surface/index.d.ts.map +0 -1
  200. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +0 -1
  201. package/dist/types/src/cli/commands/disable.d.ts.map +0 -1
  202. package/dist/types/src/cli/commands/enable.d.ts.map +0 -1
  203. package/dist/types/src/cli/commands/index.d.ts.map +0 -1
  204. package/dist/types/src/cli/commands/list.d.ts +0 -7
  205. package/dist/types/src/cli/commands/list.d.ts.map +0 -1
  206. package/dist/types/src/cli/commands/util.d.ts.map +0 -1
  207. package/dist/types/src/cli/index.d.ts +0 -3
  208. package/dist/types/src/cli/index.d.ts.map +0 -1
  209. package/dist/types/src/cli/plugin.d.ts.map +0 -1
  210. package/dist/types/src/cli/storage.d.ts.map +0 -1
  211. package/dist/types/src/containers/PluginRegistry/PluginRegistry.d.ts +0 -8
  212. package/dist/types/src/containers/PluginRegistry/PluginRegistry.d.ts.map +0 -1
  213. package/dist/types/src/containers/PluginRegistry/index.d.ts +0 -3
  214. package/dist/types/src/containers/PluginRegistry/index.d.ts.map +0 -1
  215. package/src/capabilities/app-graph-builder/app-graph-builder.ts +0 -137
  216. package/src/capabilities/app-graph-builder/index.ts +0 -7
  217. package/src/capabilities/operation-resolver/index.ts +0 -7
  218. package/src/capabilities/operation-resolver/operation-resolver.ts +0 -24
  219. package/src/capabilities/react-surface/index.ts +0 -7
  220. package/src/capabilities/react-surface/react-surface.tsx +0 -88
  221. package/src/cli/index.ts +0 -6
  222. package/src/containers/PluginRegistry/PluginRegistry.tsx +0 -86
  223. package/src/containers/PluginRegistry/index.ts +0 -7
  224. /package/dist/types/src/capabilities/{app-graph-builder/app-graph-builder.d.ts → app-graph-builder.d.ts} +0 -0
  225. /package/dist/types/src/capabilities/{react-surface/react-surface.d.ts → react-surface.d.ts} +0 -0
  226. /package/dist/types/src/{cli/commands → commands}/index.d.ts +0 -0
  227. /package/dist/types/src/{cli/commands → commands}/util.d.ts +0 -0
  228. /package/dist/types/src/{cli/storage.d.ts → storage.d.ts} +0 -0
  229. /package/src/{cli/commands → commands}/disable.ts +0 -0
  230. /package/src/{cli/commands → commands}/enable.ts +0 -0
  231. /package/src/{cli/commands → commands}/index.ts +0 -0
  232. /package/src/{cli/commands → commands}/list.ts +0 -0
  233. /package/src/{cli/commands → commands}/util.ts +0 -0
  234. /package/src/{cli/storage.ts → storage.ts} +0 -0
@@ -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';
@@ -3,4 +3,6 @@
3
3
  //
4
4
 
5
5
  export * from './PluginDetail';
6
+ export * from './PluginFailureBadge';
6
7
  export * from './PluginList';
8
+ export * from './RegistrySettings';
@@ -0,0 +1,163 @@
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/ui-theme';
16
+
17
+ import { PluginList, type PluginListProps } from '#components';
18
+ import { getPluginPath, meta } from '#meta';
19
+
20
+ const matchesFilter = (plugin: Plugin.Plugin, query: string) => {
21
+ const haystack = `${plugin.meta.name ?? ''} ${plugin.meta.id}`.toLowerCase();
22
+ return haystack.includes(query);
23
+ };
24
+
25
+ export type BaseRegistryArticleProps = {
26
+ /** Article id used as the pivotId when opening a plugin's detail surface. */
27
+ id: string;
28
+ /** Plugins to display, pre-sorted by the caller. */
29
+ plugins: readonly Plugin.Plugin[];
30
+ /**
31
+ * Distinguishes the source of toggle events for observability
32
+ * (e.g. `'registry'` for the public registry surface).
33
+ */
34
+ source?: string;
35
+ /** Rendered in place of the list when no plugins match the current filter. */
36
+ empty?: ReactNode;
37
+ } & Pick<
38
+ PluginListProps,
39
+ | 'installed'
40
+ | 'installing'
41
+ | 'updating'
42
+ | 'updateAvailableIds'
43
+ | 'extraTagsById'
44
+ | 'failuresById'
45
+ | 'onInstall'
46
+ | 'onUpdate'
47
+ >;
48
+
49
+ export const BaseRegistryArticle = composable<HTMLDivElement, BaseRegistryArticleProps>(
50
+ (
51
+ {
52
+ id,
53
+ plugins,
54
+ source,
55
+ empty,
56
+ installed,
57
+ installing,
58
+ updating,
59
+ updateAvailableIds,
60
+ extraTagsById,
61
+ failuresById,
62
+ onInstall,
63
+ onUpdate,
64
+ ...props
65
+ },
66
+ forwardedRef,
67
+ ) => {
68
+ const { t } = useTranslation(meta.id);
69
+ const manager = usePluginManager();
70
+ const { invoke, invokePromise } = useOperationInvoker();
71
+ const allSettings = useCapabilities(AppCapabilities.Settings);
72
+ const enabled = useAtomValue(manager.enabled);
73
+ const [filter, setFilter] = useState('');
74
+
75
+ const filtered = useMemo(() => {
76
+ const query = filter.trim().toLowerCase();
77
+ return query.length === 0 ? plugins : plugins.filter((plugin) => matchesFilter(plugin, query));
78
+ }, [plugins, filter]);
79
+
80
+ const handleChange = useCallback(
81
+ (pluginId: string, nextEnabled: boolean) =>
82
+ Effect.gen(function* () {
83
+ if (nextEnabled) {
84
+ yield* manager.enable(pluginId);
85
+ } else {
86
+ yield* manager.disable(pluginId);
87
+ }
88
+ yield* invoke(ObservabilityOperation.SendEvent, {
89
+ name: 'plugins.toggle',
90
+ properties: source
91
+ ? { plugin: pluginId, enabled: nextEnabled, source }
92
+ : { plugin: pluginId, enabled: nextEnabled },
93
+ });
94
+ }).pipe(runAndForwardErrors),
95
+ [invoke, manager, source],
96
+ );
97
+
98
+ const handleClick = useCallback(
99
+ (pluginId: string) =>
100
+ invokePromise(LayoutOperation.Open, {
101
+ subject: [getPluginPath(pluginId)],
102
+ pivotId: id,
103
+ positioning: 'end',
104
+ }),
105
+ [invokePromise, id],
106
+ );
107
+
108
+ const hasSettings = useCallback(
109
+ (pluginId: string) => allSettings.some((setting) => setting.prefix === pluginId),
110
+ [allSettings],
111
+ );
112
+
113
+ const handleSettings = useCallback(
114
+ (pluginId: string) => invokePromise(SettingsOperation.Open, { plugin: pluginId }),
115
+ [invokePromise],
116
+ );
117
+
118
+ return (
119
+ <Panel.Root {...composableProps(props)} ref={forwardedRef}>
120
+ <Panel.Toolbar asChild>
121
+ <Toolbar.Root>
122
+ <Input.Root>
123
+ <Input.Label srOnly>{t('filter.label')}</Input.Label>
124
+ <Input.TextInput
125
+ placeholder={t('filter.placeholder')}
126
+ value={filter}
127
+ onChange={(event) => setFilter(event.target.value)}
128
+ />
129
+ </Input.Root>
130
+ </Toolbar.Root>
131
+ </Panel.Toolbar>
132
+ <Panel.Content asChild>
133
+ <ScrollArea.Root orientation='vertical'>
134
+ <ScrollArea.Viewport>
135
+ {filtered.length > 0 ? (
136
+ <PluginList
137
+ plugins={filtered}
138
+ enabled={enabled}
139
+ installed={installed}
140
+ installing={installing}
141
+ updating={updating}
142
+ updateAvailableIds={updateAvailableIds}
143
+ extraTagsById={extraTagsById}
144
+ failuresById={failuresById}
145
+ onClick={handleClick}
146
+ onChange={handleChange}
147
+ onInstall={onInstall}
148
+ onUpdate={onUpdate}
149
+ hasSettings={hasSettings}
150
+ onSettings={handleSettings}
151
+ />
152
+ ) : (
153
+ empty
154
+ )}
155
+ </ScrollArea.Viewport>
156
+ </ScrollArea.Root>
157
+ </Panel.Content>
158
+ </Panel.Root>
159
+ );
160
+ },
161
+ );
162
+
163
+ BaseRegistryArticle.displayName = 'BaseRegistryArticle';
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './BaseRegistryArticle';
@@ -0,0 +1,87 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+ import React, { useCallback, useRef, useState } from 'react';
7
+
8
+ import { usePluginManager } from '@dxos/app-framework/ui';
9
+ import { runAndForwardErrors } from '@dxos/effect';
10
+ import { Button, Dialog, Input, useTranslation } from '@dxos/react-ui';
11
+
12
+ import { meta } from '../../meta';
13
+
14
+ export const LOAD_PLUGIN_DIALOG = `${meta.id}.LoadPluginDialog`;
15
+
16
+ export const LoadPluginDialog = () => {
17
+ const manager = usePluginManager();
18
+ const { t } = useTranslation(meta.id);
19
+ const [url, setUrl] = useState('');
20
+ const [loading, setLoading] = useState(false);
21
+ const [error, setError] = useState<string | null>(null);
22
+ const closeRef = useRef<HTMLButtonElement | null>(null);
23
+
24
+ const handleLoad = useCallback(() => {
25
+ const trimmed = url.trim();
26
+ if (!trimmed) {
27
+ return;
28
+ }
29
+
30
+ setLoading(true);
31
+ setError(null);
32
+
33
+ void Effect.gen(function* () {
34
+ const plugin = yield* manager.add(trimmed);
35
+ yield* manager.enable(plugin.meta.id);
36
+ closeRef.current?.click();
37
+ }).pipe(
38
+ Effect.catchAll((err) =>
39
+ Effect.sync(() => {
40
+ setError(String(err));
41
+ }),
42
+ ),
43
+ Effect.tap(() => Effect.sync(() => setLoading(false))),
44
+ runAndForwardErrors,
45
+ );
46
+ }, [url, manager]);
47
+
48
+ return (
49
+ <Dialog.Content>
50
+ <Dialog.Header>
51
+ <Dialog.Title>{t('load-by-url-dialog.title')}</Dialog.Title>
52
+ <Dialog.Close asChild>
53
+ <Dialog.CloseIconButton ref={closeRef} />
54
+ </Dialog.Close>
55
+ </Dialog.Header>
56
+ <Dialog.Body>
57
+ {/* TODO(burdon): Form section. */}
58
+ <div className='flex flex-col gap-4'>
59
+ <Input.Root validationValence={error ? 'error' : undefined}>
60
+ <Input.Label>{t('plugin-url.label')}</Input.Label>
61
+ <Input.TextInput
62
+ placeholder='https://example.com/manifest.json'
63
+ value={url}
64
+ onChange={(event) => {
65
+ setUrl(event.target.value);
66
+ setError(null);
67
+ }}
68
+ onKeyDown={(event) => {
69
+ if (event.key === 'Enter') {
70
+ handleLoad();
71
+ }
72
+ }}
73
+ disabled={loading}
74
+ autoFocus
75
+ />
76
+ {error && <Input.DescriptionAndValidation>{error}</Input.DescriptionAndValidation>}
77
+ </Input.Root>
78
+ <div className='flex justify-end'>
79
+ <Button variant='primary' disabled={!url.trim() || loading} onClick={handleLoad}>
80
+ {loading ? t('loading.label') : t('load-plugin.label')}
81
+ </Button>
82
+ </div>
83
+ </div>
84
+ </Dialog.Body>
85
+ </Dialog.Content>
86
+ );
87
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export { LoadPluginDialog as default } from './LoadPluginDialog';