@dxos/plugin-registry 0.8.4-main.9be5663bfe → 0.8.4-main.abd8ff62ef

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 (123) hide show
  1. package/dist/lib/browser/cli/index.mjs +1 -188
  2. package/dist/lib/browser/cli/index.mjs.map +4 -4
  3. package/dist/lib/browser/index.mjs +5 -45
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/translations.mjs +35 -0
  7. package/dist/lib/browser/translations.mjs.map +7 -0
  8. package/dist/lib/node-esm/cli/index.mjs +1 -188
  9. package/dist/lib/node-esm/cli/index.mjs.map +4 -4
  10. package/dist/lib/node-esm/index.mjs +5 -45
  11. package/dist/lib/node-esm/index.mjs.map +4 -4
  12. package/dist/lib/node-esm/meta.json +1 -1
  13. package/dist/lib/node-esm/translations.mjs +37 -0
  14. package/dist/lib/node-esm/translations.mjs.map +7 -0
  15. package/dist/types/src/RegistryPlugin.d.ts +1 -0
  16. package/dist/types/src/RegistryPlugin.d.ts.map +1 -1
  17. package/dist/types/src/{cli/plugin.d.ts → RegistryPlugin.node.d.ts} +2 -1
  18. package/dist/types/src/RegistryPlugin.node.d.ts.map +1 -0
  19. package/dist/types/src/RegistryPlugin.test.d.ts +2 -0
  20. package/dist/types/src/RegistryPlugin.test.d.ts.map +1 -0
  21. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  22. package/dist/types/src/capabilities/index.d.ts +2 -1
  23. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  24. package/dist/types/src/capabilities/operation-handler.d.ts +1 -1
  25. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -1
  26. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  27. package/dist/types/src/capabilities/state.d.ts +7 -0
  28. package/dist/types/src/capabilities/state.d.ts.map +1 -0
  29. package/dist/types/src/cli/index.d.ts +1 -2
  30. package/dist/types/src/cli/index.d.ts.map +1 -1
  31. package/dist/types/src/{cli/commands → commands}/disable.d.ts +2 -2
  32. package/dist/types/src/commands/disable.d.ts.map +1 -0
  33. package/dist/types/src/{cli/commands → commands}/enable.d.ts +2 -2
  34. package/dist/types/src/commands/enable.d.ts.map +1 -0
  35. package/dist/types/src/commands/index.d.ts.map +1 -0
  36. package/dist/types/src/commands/list.d.ts +7 -0
  37. package/dist/types/src/commands/list.d.ts.map +1 -0
  38. package/dist/types/src/commands/util.d.ts.map +1 -0
  39. package/dist/types/src/components/PluginDetail/PluginDetail.d.ts +12 -1
  40. package/dist/types/src/components/PluginDetail/PluginDetail.d.ts.map +1 -1
  41. package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts +23 -20
  42. package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts.map +1 -1
  43. package/dist/types/src/components/PluginList/PluginItem.d.ts +14 -1
  44. package/dist/types/src/components/PluginList/PluginItem.d.ts.map +1 -1
  45. package/dist/types/src/components/PluginList/PluginList.d.ts +7 -2
  46. package/dist/types/src/components/PluginList/PluginList.d.ts.map +1 -1
  47. package/dist/types/src/components/PluginList/PluginList.stories.d.ts +23 -20
  48. package/dist/types/src/components/PluginList/PluginList.stories.d.ts.map +1 -1
  49. package/dist/types/src/containers/CommunityRegistry/CommunityRegistry.d.ts +8 -0
  50. package/dist/types/src/containers/CommunityRegistry/CommunityRegistry.d.ts.map +1 -0
  51. package/dist/types/src/containers/CommunityRegistry/index.d.ts +2 -0
  52. package/dist/types/src/containers/CommunityRegistry/index.d.ts.map +1 -0
  53. package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts.map +1 -1
  54. package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts.map +1 -1
  55. package/dist/types/src/containers/PluginRegistry/PluginRegistry.d.ts +5 -1
  56. package/dist/types/src/containers/PluginRegistry/PluginRegistry.d.ts.map +1 -1
  57. package/dist/types/src/containers/index.d.ts +1 -0
  58. package/dist/types/src/containers/index.d.ts.map +1 -1
  59. package/dist/types/src/hooks/index.d.ts +3 -0
  60. package/dist/types/src/hooks/index.d.ts.map +1 -0
  61. package/dist/types/src/hooks/use-auto-tags.d.ts +20 -0
  62. package/dist/types/src/hooks/use-auto-tags.d.ts.map +1 -0
  63. package/dist/types/src/hooks/use-community-plugins.d.ts +16 -0
  64. package/dist/types/src/hooks/use-community-plugins.d.ts.map +1 -0
  65. package/dist/types/src/index.d.ts +2 -1
  66. package/dist/types/src/index.d.ts.map +1 -1
  67. package/dist/types/src/meta.d.ts.map +1 -1
  68. package/dist/types/src/operations/index.d.ts +1 -1
  69. package/dist/types/src/operations/index.d.ts.map +1 -1
  70. package/dist/types/src/operations/open-plugin-registry.d.ts +1 -1
  71. package/dist/types/src/operations/open-plugin-registry.d.ts.map +1 -1
  72. package/dist/types/src/storage.d.ts.map +1 -0
  73. package/dist/types/src/translations.d.ts +22 -20
  74. package/dist/types/src/translations.d.ts.map +1 -1
  75. package/dist/types/src/types.d.ts +12 -1
  76. package/dist/types/src/types.d.ts.map +1 -1
  77. package/dist/types/tsconfig.tsbuildinfo +1 -1
  78. package/package.json +41 -25
  79. package/src/{cli/plugin.ts → RegistryPlugin.node.ts} +2 -0
  80. package/src/RegistryPlugin.test.ts +24 -0
  81. package/src/RegistryPlugin.tsx +9 -3
  82. package/src/capabilities/app-graph-builder.ts +61 -17
  83. package/src/capabilities/index.ts +2 -1
  84. package/src/capabilities/operation-handler.ts +1 -1
  85. package/src/capabilities/react-surface.tsx +49 -24
  86. package/src/capabilities/state.ts +38 -0
  87. package/src/cli/index.ts +1 -2
  88. package/src/components/PluginDetail/PluginDetail.stories.tsx +2 -1
  89. package/src/components/PluginDetail/PluginDetail.tsx +27 -5
  90. package/src/components/PluginList/PluginItem.tsx +71 -8
  91. package/src/components/PluginList/PluginList.stories.tsx +1 -1
  92. package/src/components/PluginList/PluginList.tsx +8 -3
  93. package/src/containers/CommunityRegistry/CommunityRegistry.tsx +181 -0
  94. package/src/containers/CommunityRegistry/index.ts +5 -0
  95. package/src/containers/LoadPluginDialog/LoadPluginDialog.tsx +2 -1
  96. package/src/containers/PluginArticle/PluginArticle.tsx +47 -2
  97. package/src/containers/PluginRegistry/PluginRegistry.tsx +6 -2
  98. package/src/containers/index.ts +1 -0
  99. package/src/hooks/index.ts +6 -0
  100. package/src/hooks/use-auto-tags.ts +52 -0
  101. package/src/hooks/use-community-plugins.ts +35 -0
  102. package/src/index.ts +5 -1
  103. package/src/operations/index.ts +1 -1
  104. package/src/operations/open-plugin-registry.ts +1 -1
  105. package/src/translations.ts +5 -2
  106. package/src/types.ts +26 -1
  107. package/dist/types/src/cli/commands/disable.d.ts.map +0 -1
  108. package/dist/types/src/cli/commands/enable.d.ts.map +0 -1
  109. package/dist/types/src/cli/commands/index.d.ts.map +0 -1
  110. package/dist/types/src/cli/commands/list.d.ts +0 -7
  111. package/dist/types/src/cli/commands/list.d.ts.map +0 -1
  112. package/dist/types/src/cli/commands/util.d.ts.map +0 -1
  113. package/dist/types/src/cli/plugin.d.ts.map +0 -1
  114. package/dist/types/src/cli/storage.d.ts.map +0 -1
  115. /package/dist/types/src/{cli/commands → commands}/index.d.ts +0 -0
  116. /package/dist/types/src/{cli/commands → commands}/util.d.ts +0 -0
  117. /package/dist/types/src/{cli/storage.d.ts → storage.d.ts} +0 -0
  118. /package/src/{cli/commands → commands}/disable.ts +0 -0
  119. /package/src/{cli/commands → commands}/enable.ts +0 -0
  120. /package/src/{cli/commands → commands}/index.ts +0 -0
  121. /package/src/{cli/commands → commands}/list.ts +0 -0
  122. /package/src/{cli/commands → commands}/util.ts +0 -0
  123. /package/src/{cli/storage.ts → storage.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-registry",
3
- "version": "0.8.4-main.9be5663bfe",
3
+ "version": "0.8.4-main.abd8ff62ef",
4
4
  "description": "Braneframe plugin gallery",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -18,6 +18,12 @@
18
18
  "#containers": "./src/containers/index.ts",
19
19
  "#meta": "./src/meta.ts",
20
20
  "#operations": "./src/operations/index.ts",
21
+ "#translations": "./src/translations.ts",
22
+ "#plugin": {
23
+ "browser": "./src/RegistryPlugin.tsx",
24
+ "node": "./src/RegistryPlugin.node.ts",
25
+ "default": "./src/RegistryPlugin.tsx"
26
+ },
21
27
  "#types": "./src/types.ts"
22
28
  },
23
29
  "exports": {
@@ -32,12 +38,15 @@
32
38
  "browser": "./dist/lib/browser/cli/index.mjs",
33
39
  "node": "./dist/lib/node-esm/cli/index.mjs",
34
40
  "types": "./dist/types/src/cli/index.d.ts"
41
+ },
42
+ "./translations": {
43
+ "source": "./src/translations.ts",
44
+ "types": "./dist/types/src/translations.d.ts",
45
+ "browser": "./dist/lib/browser/translations.mjs",
46
+ "node": "./dist/lib/node-esm/translations.mjs"
35
47
  }
36
48
  },
37
49
  "types": "dist/types/src/index.d.ts",
38
- "typesVersions": {
39
- "*": {}
40
- },
41
50
  "files": [
42
51
  "dist",
43
52
  "src"
@@ -46,20 +55,26 @@
46
55
  "@effect-atom/atom-react": "^0.5.0",
47
56
  "@effect/cli": "0.73.2",
48
57
  "yaml": "^2.8.2",
49
- "@dxos/app-toolkit": "0.8.4-main.9be5663bfe",
50
- "@dxos/app-framework": "0.8.4-main.9be5663bfe",
51
- "@dxos/cli-util": "0.8.4-main.9be5663bfe",
52
- "@dxos/client-protocol": "0.8.4-main.9be5663bfe",
53
- "@dxos/echo": "0.8.4-main.9be5663bfe",
54
- "@dxos/invariant": "0.8.4-main.9be5663bfe",
55
- "@dxos/plugin-graph": "0.8.4-main.9be5663bfe",
56
- "@dxos/effect": "0.8.4-main.9be5663bfe",
57
- "@dxos/plugin-observability": "0.8.4-main.9be5663bfe",
58
- "@dxos/react-ui-form": "0.8.4-main.9be5663bfe",
59
- "@dxos/react-ui-mosaic": "0.8.4-main.9be5663bfe",
60
- "@dxos/util": "0.8.4-main.9be5663bfe",
61
- "@dxos/react-ui-stack": "0.8.4-main.9be5663bfe",
62
- "@dxos/operation": "0.8.4-main.9be5663bfe"
58
+ "@dxos/app-framework": "0.8.4-main.abd8ff62ef",
59
+ "@dxos/app-toolkit": "0.8.4-main.abd8ff62ef",
60
+ "@dxos/cli-util": "0.8.4-main.abd8ff62ef",
61
+ "@dxos/client-protocol": "0.8.4-main.abd8ff62ef",
62
+ "@dxos/context": "0.8.4-main.abd8ff62ef",
63
+ "@dxos/echo": "0.8.4-main.abd8ff62ef",
64
+ "@dxos/edge-client": "0.8.4-main.abd8ff62ef",
65
+ "@dxos/effect": "0.8.4-main.abd8ff62ef",
66
+ "@dxos/compute": "0.8.4-main.abd8ff62ef",
67
+ "@dxos/invariant": "0.8.4-main.abd8ff62ef",
68
+ "@dxos/plugin-graph": "0.8.4-main.abd8ff62ef",
69
+ "@dxos/log": "0.8.4-main.abd8ff62ef",
70
+ "@dxos/plugin-observability": "0.8.4-main.abd8ff62ef",
71
+ "@dxos/protocols": "0.8.4-main.abd8ff62ef",
72
+ "@dxos/react-ui-form": "0.8.4-main.abd8ff62ef",
73
+ "@dxos/react-ui-mosaic": "0.8.4-main.abd8ff62ef",
74
+ "@dxos/react-edge-client": "0.8.4-main.abd8ff62ef",
75
+ "@dxos/react-ui-stack": "0.8.4-main.abd8ff62ef",
76
+ "@dxos/util": "0.8.4-main.abd8ff62ef",
77
+ "@dxos/plugin-client": "0.8.4-main.abd8ff62ef"
63
78
  },
64
79
  "devDependencies": {
65
80
  "@effect-atom/atom-react": "^0.5.0",
@@ -69,11 +84,12 @@
69
84
  "effect": "3.20.0",
70
85
  "react": "~19.2.3",
71
86
  "react-dom": "~19.2.3",
72
- "vite": "^7.1.11",
73
- "@dxos/random": "0.8.4-main.9be5663bfe",
74
- "@dxos/storybook-utils": "0.8.4-main.9be5663bfe",
75
- "@dxos/ui-theme": "0.8.4-main.9be5663bfe",
76
- "@dxos/react-ui": "0.8.4-main.9be5663bfe"
87
+ "vite": "^8.0.10",
88
+ "@dxos/plugin-testing": "0.8.4-main.abd8ff62ef",
89
+ "@dxos/react-ui": "0.8.4-main.abd8ff62ef",
90
+ "@dxos/storybook-utils": "0.8.4-main.abd8ff62ef",
91
+ "@dxos/random": "0.8.4-main.abd8ff62ef",
92
+ "@dxos/ui-theme": "0.8.4-main.abd8ff62ef"
77
93
  },
78
94
  "peerDependencies": {
79
95
  "@effect-atom/atom-react": "^0.5.0",
@@ -81,8 +97,8 @@
81
97
  "effect": "3.20.0",
82
98
  "react": "~19.2.3",
83
99
  "react-dom": "~19.2.3",
84
- "@dxos/react-ui": "0.8.4-main.9be5663bfe",
85
- "@dxos/ui-theme": "0.8.4-main.9be5663bfe"
100
+ "@dxos/react-ui": "0.8.4-main.abd8ff62ef",
101
+ "@dxos/ui-theme": "0.8.4-main.abd8ff62ef"
86
102
  },
87
103
  "publishConfig": {
88
104
  "access": "public"
@@ -15,3 +15,5 @@ export const RegistryPlugin = Plugin.define(meta).pipe(
15
15
  }),
16
16
  Plugin.make,
17
17
  );
18
+
19
+ export default RegistryPlugin;
@@ -0,0 +1,24 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { describe, test } from 'vitest';
6
+
7
+ import { createComposerTestApp } from '@dxos/plugin-testing/harness';
8
+
9
+ import { RegistryPlugin } from '#plugin';
10
+
11
+ import { meta } from './meta';
12
+
13
+ const moduleId = (name: string) => `${meta.id}.module.${name}`;
14
+
15
+ describe('RegistryPlugin', () => {
16
+ test('modules activate on the expected events', async ({ expect }) => {
17
+ await using harness = await createComposerTestApp({
18
+ plugins: [RegistryPlugin()],
19
+ });
20
+
21
+ // CLI commands activate on startup.
22
+ expect(harness.manager.getActive()).toContain(moduleId('cli-commands'));
23
+ });
24
+ });
@@ -4,16 +4,22 @@
4
4
 
5
5
  import { Plugin } from '@dxos/app-framework';
6
6
  import { AppPlugin } from '@dxos/app-toolkit';
7
+ import { ClientEvents } from '@dxos/plugin-client/types';
7
8
 
8
- import { AppGraphBuilder, OperationHandler, ReactSurface } from '#capabilities';
9
+ import { AppGraphBuilder, OperationHandler, ReactSurface, RegistryState } from '#capabilities';
9
10
  import { meta } from '#meta';
10
-
11
- import { translations } from './translations';
11
+ import { translations } from '#translations';
12
12
 
13
13
  export const RegistryPlugin = Plugin.define(meta).pipe(
14
14
  AppPlugin.addAppGraphModule({ activate: AppGraphBuilder }),
15
15
  AppPlugin.addOperationHandlerModule({ activate: OperationHandler }),
16
16
  AppPlugin.addSurfaceModule({ activate: ReactSurface }),
17
17
  AppPlugin.addTranslationsModule({ translations }),
18
+ Plugin.addModule({
19
+ activatesOn: ClientEvents.ClientReady,
20
+ activate: RegistryState,
21
+ }),
18
22
  Plugin.make,
19
23
  );
24
+
25
+ export default RegistryPlugin;
@@ -3,17 +3,31 @@
3
3
  //
4
4
 
5
5
  import * as Effect from 'effect/Effect';
6
- import * as Option from 'effect/Option';
7
6
 
8
- import { Capabilities, Capability } from '@dxos/app-framework';
7
+ import { Capabilities, Capability, Plugin } from '@dxos/app-framework';
9
8
  import { AppCapabilities, LayoutOperation, SettingsOperation } from '@dxos/app-toolkit';
10
- import { Operation } from '@dxos/operation';
9
+ import { Operation } from '@dxos/compute';
11
10
  import { GraphBuilder, Node, NodeMatcher } from '@dxos/plugin-graph';
11
+ import { type PluginEntry } from '@dxos/protocols';
12
12
 
13
13
  import { REGISTRY_ID, REGISTRY_KEY, registryCategoryId, meta } from '#meta';
14
+ import { RegistryCapabilities } from '#types';
14
15
 
15
16
  import { LOAD_PLUGIN_DIALOG } from '../containers';
16
17
 
18
+ /**
19
+ * Turns a community manifest entry into a minimal {@link Plugin.Plugin} so it
20
+ * can be attached as the graph node's `data`. The synthesized plugin has no
21
+ * modules and only exists so the article surface can render details for
22
+ * community plugins that haven't been installed yet.
23
+ */
24
+ const toDisplayPlugin = (entry: PluginEntry): Plugin.Plugin =>
25
+ ({
26
+ [Plugin.PluginTypeId]: Plugin.PluginTypeId,
27
+ meta: entry.meta,
28
+ modules: [],
29
+ }) as Plugin.Plugin;
30
+
17
31
  export default Capability.makeModule(
18
32
  Effect.fnUntraced(function* () {
19
33
  const capabilities = yield* Capability.Service;
@@ -47,18 +61,19 @@ export default Capability.makeModule(
47
61
  label: ['plugin-registry.label', { ns: meta.id }],
48
62
  icon: 'ph--squares-four--regular',
49
63
  disposition: 'pin-end',
64
+ position: 'hoist',
50
65
  testId: 'treeView.pluginRegistry',
51
66
  },
52
67
  nodes: [
53
68
  Node.make({
54
- id: registryCategoryId('all'),
69
+ id: registryCategoryId('official'),
55
70
  type: 'category',
56
- data: registryCategoryId('all'),
71
+ data: registryCategoryId('official'),
57
72
  properties: {
58
- label: ['all-plugins.label', { ns: meta.id }],
73
+ label: ['official-plugins.label', { ns: meta.id }],
59
74
  icon: 'ph--squares-four--regular',
60
75
  key: REGISTRY_KEY,
61
- testId: 'pluginRegistry.all',
76
+ testId: 'pluginRegistry.official',
62
77
  },
63
78
  }),
64
79
  Node.make({
@@ -94,15 +109,24 @@ export default Capability.makeModule(
94
109
  testId: 'pluginRegistry.labs',
95
110
  },
96
111
  }),
112
+ Node.make({
113
+ id: registryCategoryId('community'),
114
+ type: 'category',
115
+ data: registryCategoryId('community'),
116
+ properties: {
117
+ label: ['community-plugins.label', { ns: meta.id }],
118
+ icon: 'ph--users-three--regular',
119
+ key: REGISTRY_KEY,
120
+ testId: 'pluginRegistry.community',
121
+ },
122
+ }),
97
123
  ],
98
124
  }),
99
125
  ]),
100
126
  }),
101
127
  GraphBuilder.createExtension({
102
128
  id: 'actions',
103
- match: NodeMatcher.whenAny(NodeMatcher.whenId(`root/${REGISTRY_ID}`), (node) =>
104
- node.properties.key === REGISTRY_KEY ? Option.some(node) : Option.none(),
105
- ),
129
+ match: NodeMatcher.whenId(`root/${REGISTRY_ID}`),
106
130
  actions: () =>
107
131
  Effect.succeed([
108
132
  {
@@ -124,11 +148,30 @@ export default Capability.makeModule(
124
148
  GraphBuilder.createExtension({
125
149
  id: 'plugins',
126
150
  match: NodeMatcher.whenId(`root/${REGISTRY_ID}`),
127
- connector: () => {
151
+ connector: (_node, get) => {
128
152
  const manager = capabilities.get(Capabilities.PluginManager);
129
- return Effect.succeed(
130
- manager.getPlugins().map((plugin) =>
131
- Node.make({
153
+ const installedIds = new Set(manager.getPlugins().map((plugin) => plugin.meta.id));
154
+ const stateAtom = capabilities.getAll(RegistryCapabilities.State).at(0);
155
+
156
+ const installedNodes = manager.getPlugins().map((plugin) =>
157
+ Node.make({
158
+ id: plugin.meta.id,
159
+ type: 'org.dxos.plugin',
160
+ data: plugin,
161
+ properties: {
162
+ label: plugin.meta.name ?? plugin.meta.id,
163
+ icon: plugin.meta.icon ?? 'ph--circle--regular',
164
+ disposition: 'hidden',
165
+ },
166
+ }),
167
+ );
168
+
169
+ const communityEntries = stateAtom ? get(stateAtom).entries : [];
170
+ const communityNodes = communityEntries
171
+ .filter((entry) => !installedIds.has(entry.meta.id))
172
+ .map((entry) => {
173
+ const plugin = toDisplayPlugin(entry);
174
+ return Node.make({
132
175
  id: plugin.meta.id,
133
176
  type: 'org.dxos.plugin',
134
177
  data: plugin,
@@ -137,9 +180,10 @@ export default Capability.makeModule(
137
180
  icon: plugin.meta.icon ?? 'ph--circle--regular',
138
181
  disposition: 'hidden',
139
182
  },
140
- }),
141
- ),
142
- );
183
+ });
184
+ });
185
+
186
+ return Effect.succeed([...installedNodes, ...communityNodes]);
143
187
  },
144
188
  }),
145
189
  ]);
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import { Capability } from '@dxos/app-framework';
6
- import { OperationHandlerSet } from '@dxos/operation';
6
+ import { OperationHandlerSet } from '@dxos/compute';
7
7
 
8
8
  export const AppGraphBuilder = Capability.lazy('AppGraphBuilder', () => import('./app-graph-builder'));
9
9
  export const OperationHandler = Capability.lazy<OperationHandlerSet.OperationHandlerSet>(
@@ -11,3 +11,4 @@ export const OperationHandler = Capability.lazy<OperationHandlerSet.OperationHan
11
11
  () => import('./operation-handler'),
12
12
  );
13
13
  export const ReactSurface = Capability.lazy('ReactSurface', () => import('./react-surface'));
14
+ export const RegistryState = Capability.lazy('RegistryState', () => import('./state'));
@@ -5,7 +5,7 @@
5
5
  import * as Effect from 'effect/Effect';
6
6
 
7
7
  import { Capabilities, Capability } from '@dxos/app-framework';
8
- import type { OperationHandlerSet } from '@dxos/operation';
8
+ import type { OperationHandlerSet } from '@dxos/compute';
9
9
 
10
10
  import { RegistryOperationHandlerSet } from '#operations';
11
11
 
@@ -5,89 +5,114 @@
5
5
  import * as Effect from 'effect/Effect';
6
6
  import React, { useMemo } from 'react';
7
7
 
8
- import { Capabilities, Capability } from '@dxos/app-framework';
8
+ import { Capabilities, Capability, Plugin } from '@dxos/app-framework';
9
9
  import { Surface, usePluginManager } from '@dxos/app-framework/ui';
10
10
  import { AppSurface } from '@dxos/app-toolkit/ui';
11
11
 
12
- import { LOAD_PLUGIN_DIALOG, LoadPluginDialog, PluginArticle, PluginRegistry } from '#containers';
12
+ import { CommunityRegistry, LOAD_PLUGIN_DIALOG, LoadPluginDialog, PluginArticle, PluginRegistry } from '#containers';
13
13
  import { registryCategoryId } from '#meta';
14
14
 
15
+ import { useAutoTags, useCommunityPlugins, useRemotePluginIds } from '../hooks';
16
+
15
17
  export default Capability.makeModule(() =>
16
18
  Effect.succeed(
17
19
  Capability.contributes(Capabilities.ReactSurface, [
18
20
  Surface.create({
19
- id: 'all',
20
- role: 'article',
21
- filter: AppSurface.literalSection(registryCategoryId('all')),
21
+ id: 'official',
22
+ filter: AppSurface.literal(AppSurface.Article, registryCategoryId('official')),
22
23
  component: () => {
23
24
  const manager = usePluginManager();
25
+ const remoteIds = useRemotePluginIds();
26
+ const { entries } = useCommunityPlugins();
27
+ const extraTagsById = useAutoTags(entries);
24
28
  const filtered = useMemo(
25
- () => manager.getPlugins().filter(({ meta }) => !manager.getCore().includes(meta.id)),
26
- [],
29
+ () =>
30
+ manager
31
+ .getPlugins()
32
+ .filter(({ meta }) => !manager.getCore().includes(meta.id))
33
+ .filter(({ meta }) => !remoteIds.has(meta.id)),
34
+ [manager, remoteIds],
27
35
  );
28
36
 
29
- return <PluginRegistry id={registryCategoryId('all')} plugins={filtered} />;
37
+ return (
38
+ <PluginRegistry id={registryCategoryId('official')} plugins={filtered} extraTagsById={extraTagsById} />
39
+ );
30
40
  },
31
41
  }),
32
42
  Surface.create({
33
43
  id: 'installed',
34
- role: 'article',
35
- filter: AppSurface.literalSection(registryCategoryId('installed')),
44
+ filter: AppSurface.literal(AppSurface.Article, registryCategoryId('installed')),
36
45
  component: () => {
37
46
  const manager = usePluginManager();
47
+ const { entries } = useCommunityPlugins();
48
+ const extraTagsById = useAutoTags(entries);
38
49
  const filtered = useMemo(
39
50
  () =>
40
51
  manager
41
52
  .getPlugins()
42
53
  .filter(({ meta }) => !manager.getCore().includes(meta.id))
43
54
  .filter(({ meta }) => manager.getEnabled().includes(meta.id)),
44
- [],
55
+ [manager],
45
56
  );
46
57
 
47
- return <PluginRegistry id={registryCategoryId('installed')} plugins={filtered} />;
58
+ return (
59
+ <PluginRegistry id={registryCategoryId('installed')} plugins={filtered} extraTagsById={extraTagsById} />
60
+ );
48
61
  },
49
62
  }),
50
63
  Surface.create({
51
64
  id: 'recommended',
52
- role: 'article',
53
- filter: AppSurface.literalSection(registryCategoryId('recommended')),
65
+ filter: AppSurface.literal(AppSurface.Article, registryCategoryId('recommended')),
54
66
  component: () => {
55
67
  const manager = usePluginManager();
68
+ const remoteIds = useRemotePluginIds();
69
+ const { entries } = useCommunityPlugins();
70
+ const extraTagsById = useAutoTags(entries);
56
71
  const filtered = useMemo(
57
72
  () =>
58
73
  manager
59
74
  .getPlugins()
60
75
  .filter(({ meta }) => !manager.getCore().includes(meta.id))
76
+ .filter(({ meta }) => !remoteIds.has(meta.id))
61
77
  .filter(({ meta }) => !meta.tags?.includes('labs')),
62
- [],
78
+ [manager, remoteIds],
63
79
  );
64
80
 
65
- return <PluginRegistry id={registryCategoryId('recommended')} plugins={filtered} />;
81
+ return (
82
+ <PluginRegistry id={registryCategoryId('recommended')} plugins={filtered} extraTagsById={extraTagsById} />
83
+ );
66
84
  },
67
85
  }),
68
86
  Surface.create({
69
87
  id: 'labs',
70
- role: 'article',
71
- filter: AppSurface.literalSection(registryCategoryId('labs')),
88
+ filter: AppSurface.literal(AppSurface.Article, registryCategoryId('labs')),
72
89
  component: () => {
73
90
  const manager = usePluginManager();
74
- const filtered = useMemo(() => manager.getPlugins().filter(({ meta }) => meta.tags?.includes('labs')), []);
91
+ const { entries } = useCommunityPlugins();
92
+ const extraTagsById = useAutoTags(entries);
93
+ const filtered = useMemo(
94
+ () => manager.getPlugins().filter(({ meta }) => meta.tags?.includes('labs')),
95
+ [manager],
96
+ );
75
97
 
76
- return <PluginRegistry id={registryCategoryId('labs')} plugins={filtered} />;
98
+ return <PluginRegistry id={registryCategoryId('labs')} plugins={filtered} extraTagsById={extraTagsById} />;
77
99
  },
78
100
  }),
101
+ Surface.create({
102
+ id: 'community',
103
+ filter: AppSurface.literal(AppSurface.Article, registryCategoryId('community')),
104
+ component: () => <CommunityRegistry id={registryCategoryId('community')} />,
105
+ }),
79
106
  Surface.create({
80
107
  id: 'plugin-details',
81
- role: 'article',
82
- filter: AppSurface.pluginSection(),
108
+ filter: AppSurface.subject(AppSurface.Article, Plugin.isPlugin),
83
109
  component: ({ data: { subject } }) => {
84
110
  return <PluginArticle subject={subject} />;
85
111
  },
86
112
  }),
87
113
  Surface.create({
88
114
  id: LOAD_PLUGIN_DIALOG,
89
- role: 'dialog',
90
- filter: AppSurface.componentDialog(LOAD_PLUGIN_DIALOG),
115
+ filter: AppSurface.component(AppSurface.Dialog, LOAD_PLUGIN_DIALOG),
91
116
  component: () => <LoadPluginDialog />,
92
117
  }),
93
118
  ]),
@@ -0,0 +1,38 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Atom } from '@effect-atom/atom-react';
6
+ import * as Effect from 'effect/Effect';
7
+
8
+ import { Capabilities, Capability } from '@dxos/app-framework';
9
+ import { log } from '@dxos/log';
10
+ import { ClientCapabilities } from '@dxos/plugin-client/types';
11
+
12
+ import { RegistryCapabilities, type CommunityPluginsState } from '#types';
13
+
14
+ import { loadCommunityPlugins } from '../hooks';
15
+
16
+ export default Capability.makeModule(
17
+ Effect.fnUntraced(function* () {
18
+ const registry = yield* Capability.get(Capabilities.AtomRegistry);
19
+ const client = yield* Capability.get(ClientCapabilities.Client);
20
+
21
+ const communityPluginsAtom = Atom.make<CommunityPluginsState>({ entries: [], loading: true, error: null }).pipe(
22
+ Atom.keepAlive,
23
+ );
24
+
25
+ yield* loadCommunityPlugins(client.edge.http).pipe(
26
+ Effect.match({
27
+ onSuccess: (entries) => registry.set(communityPluginsAtom, { entries, loading: false, error: null }),
28
+ onFailure: (error) => {
29
+ log.catch(error);
30
+ registry.set(communityPluginsAtom, { entries: [], loading: false, error });
31
+ },
32
+ }),
33
+ Effect.forkDaemon,
34
+ );
35
+
36
+ return Capability.contributes(RegistryCapabilities.State, communityPluginsAtom);
37
+ }),
38
+ );
package/src/cli/index.ts CHANGED
@@ -2,5 +2,4 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export * from './plugin';
6
- export * from './storage';
5
+ export * from '../storage';
@@ -8,7 +8,8 @@ import { Plugin } from '@dxos/app-framework';
8
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
  import { PluginDetail } from './PluginDetail';
13
14
 
14
15
  const meta = {
@@ -5,7 +5,7 @@
5
5
  import React from 'react';
6
6
 
7
7
  import { type Plugin } from '@dxos/app-framework';
8
- import { Icon, Input, Link, ScrollArea, useTranslation } from '@dxos/react-ui';
8
+ import { Button, Icon, Input, Link, ScrollArea, useTranslation } from '@dxos/react-ui';
9
9
  import { composable, composableProps, getStyles, mx } from '@dxos/ui-theme';
10
10
 
11
11
  import { meta } from '#meta';
@@ -14,10 +14,21 @@ export type PluginDetailProps = {
14
14
  plugin: Plugin.Plugin;
15
15
  enabled?: boolean;
16
16
  onEnabledChange?: (enabled: boolean) => void;
17
+ /**
18
+ * When provided, an Uninstall button is rendered. Leave undefined for core
19
+ * or non-removable plugins.
20
+ */
21
+ onUninstall?: () => void;
22
+ /**
23
+ * When provided, the plugin is not installed and an Install button is shown
24
+ * in place of the enable Switch.
25
+ */
26
+ onInstall?: () => void;
27
+ installing?: boolean;
17
28
  };
18
29
 
19
30
  export const PluginDetail = composable<HTMLDivElement, PluginDetailProps>(
20
- ({ plugin, enabled, onEnabledChange, ...props }, forwardedRef) => {
31
+ ({ plugin, enabled, onEnabledChange, onUninstall, onInstall, installing, ...props }, forwardedRef) => {
21
32
  const { t } = useTranslation(meta.id);
22
33
  const {
23
34
  id,
@@ -41,9 +52,15 @@ export const PluginDetail = composable<HTMLDivElement, PluginDetailProps>(
41
52
  <div role='none' className='flex flex-col gap-6'>
42
53
  <div role='none' className='grid grid-cols-[1fr_min-content] gap-x-3 w-full pt-1'>
43
54
  <h2 className='text-xl'>{name}</h2>
44
- <Input.Root>
45
- <Input.Switch classNames='self-center' checked={enabled} onCheckedChange={onEnabledChange} />
46
- </Input.Root>
55
+ {onInstall ? (
56
+ <Button density='fine' variant='primary' disabled={installing} onClick={onInstall}>
57
+ {installing ? t('installing.label') : t('install.label')}
58
+ </Button>
59
+ ) : (
60
+ <Input.Root>
61
+ <Input.Switch classNames='self-center' checked={enabled} onCheckedChange={onEnabledChange} />
62
+ </Input.Root>
63
+ )}
47
64
  <p className='pt-0.5 text-sm text-description'>{id}</p>
48
65
  </div>
49
66
  <div role='none'>
@@ -73,6 +90,11 @@ export const PluginDetail = composable<HTMLDivElement, PluginDetailProps>(
73
90
  )}
74
91
  </div>
75
92
  </div>
93
+ {onUninstall && (
94
+ <div role='none'>
95
+ <Button onClick={onUninstall}>{t('uninstall.label')}</Button>
96
+ </div>
97
+ )}
76
98
  </div>
77
99
  </div>
78
100
  </ScrollArea.Viewport>