@dxos/app-framework 0.7.5-main.9d2a38b → 0.7.5-main.e94eead

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 (205) hide show
  1. package/dist/lib/browser/app-graph-builder-S4MYSHQA.mjs +137 -0
  2. package/dist/lib/browser/app-graph-builder-S4MYSHQA.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-GNLU3GAU.mjs → chunk-BCMEJONP.mjs} +660 -823
  4. package/dist/lib/browser/chunk-BCMEJONP.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-QS4J6O47.mjs +285 -0
  6. package/dist/lib/browser/chunk-QS4J6O47.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-SRZH2PQ2.mjs +32 -0
  8. package/dist/lib/browser/chunk-SRZH2PQ2.mjs.map +7 -0
  9. package/dist/lib/browser/index.mjs +57 -74
  10. package/dist/lib/browser/index.mjs.map +4 -4
  11. package/dist/lib/browser/intent-dispatcher-GFBH7T2J.mjs +11 -0
  12. package/dist/lib/browser/intent-dispatcher-GFBH7T2J.mjs.map +7 -0
  13. package/dist/lib/browser/intent-resolver-KAFM7CQH.mjs +39 -0
  14. package/dist/lib/browser/intent-resolver-KAFM7CQH.mjs.map +7 -0
  15. package/dist/lib/browser/meta.json +1 -1
  16. package/dist/lib/browser/store-L3VRR7II.mjs +29 -0
  17. package/dist/lib/browser/store-L3VRR7II.mjs.map +7 -0
  18. package/dist/lib/browser/testing/index.mjs +13 -3
  19. package/dist/lib/browser/testing/index.mjs.map +3 -3
  20. package/dist/lib/browser/worker.mjs +77 -0
  21. package/dist/lib/browser/worker.mjs.map +7 -0
  22. package/dist/lib/node/app-graph-builder-VMHWFCTP.cjs +146 -0
  23. package/dist/lib/node/app-graph-builder-VMHWFCTP.cjs.map +7 -0
  24. package/dist/lib/node/chunk-7Y6KJ3OK.cjs +1466 -0
  25. package/dist/lib/node/chunk-7Y6KJ3OK.cjs.map +7 -0
  26. package/dist/lib/node/chunk-B65NJEIJ.cjs +308 -0
  27. package/dist/lib/node/chunk-B65NJEIJ.cjs.map +7 -0
  28. package/dist/lib/node/chunk-VCIHQZSN.cjs +58 -0
  29. package/dist/lib/node/chunk-VCIHQZSN.cjs.map +7 -0
  30. package/dist/lib/node/index.cjs +106 -118
  31. package/dist/lib/node/index.cjs.map +4 -4
  32. package/dist/lib/node/intent-dispatcher-PRCC4KZT.cjs +32 -0
  33. package/dist/lib/node/intent-dispatcher-PRCC4KZT.cjs.map +7 -0
  34. package/dist/lib/node/intent-resolver-OZDKCHPW.cjs +46 -0
  35. package/dist/lib/node/intent-resolver-OZDKCHPW.cjs.map +7 -0
  36. package/dist/lib/node/meta.json +1 -1
  37. package/dist/lib/node/store-BVUKNVKL.cjs +44 -0
  38. package/dist/lib/node/store-BVUKNVKL.cjs.map +7 -0
  39. package/dist/lib/node/testing/index.cjs +18 -8
  40. package/dist/lib/node/testing/index.cjs.map +3 -3
  41. package/dist/lib/node/worker.cjs +99 -0
  42. package/dist/lib/node/worker.cjs.map +7 -0
  43. package/dist/lib/node-esm/app-graph-builder-XHI5IIXQ.mjs +138 -0
  44. package/dist/lib/node-esm/app-graph-builder-XHI5IIXQ.mjs.map +7 -0
  45. package/dist/lib/node-esm/chunk-CBT75GCX.mjs +34 -0
  46. package/dist/lib/node-esm/chunk-CBT75GCX.mjs.map +7 -0
  47. package/dist/lib/node-esm/chunk-JDAHZRYQ.mjs +286 -0
  48. package/dist/lib/node-esm/chunk-JDAHZRYQ.mjs.map +7 -0
  49. package/dist/lib/node-esm/{chunk-KPMTPXQI.mjs → chunk-TVIR2PHY.mjs} +660 -823
  50. package/dist/lib/node-esm/chunk-TVIR2PHY.mjs.map +7 -0
  51. package/dist/lib/node-esm/index.mjs +57 -74
  52. package/dist/lib/node-esm/index.mjs.map +4 -4
  53. package/dist/lib/node-esm/intent-dispatcher-LGACN32C.mjs +12 -0
  54. package/dist/lib/node-esm/intent-dispatcher-LGACN32C.mjs.map +7 -0
  55. package/dist/lib/node-esm/intent-resolver-RBNG76ZX.mjs +40 -0
  56. package/dist/lib/node-esm/intent-resolver-RBNG76ZX.mjs.map +7 -0
  57. package/dist/lib/node-esm/meta.json +1 -1
  58. package/dist/lib/node-esm/store-PHTOEREN.mjs +30 -0
  59. package/dist/lib/node-esm/store-PHTOEREN.mjs.map +7 -0
  60. package/dist/lib/node-esm/testing/index.mjs +13 -3
  61. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  62. package/dist/lib/node-esm/worker.mjs +78 -0
  63. package/dist/lib/node-esm/worker.mjs.map +7 -0
  64. package/dist/types/src/App.d.ts +2 -2
  65. package/dist/types/src/App.d.ts.map +1 -1
  66. package/dist/types/src/common/capabilities.d.ts +88 -124
  67. package/dist/types/src/common/capabilities.d.ts.map +1 -1
  68. package/dist/types/src/common/events.d.ts +22 -11
  69. package/dist/types/src/common/events.d.ts.map +1 -1
  70. package/dist/types/src/common/file.d.ts +1 -1
  71. package/dist/types/src/common/file.d.ts.map +1 -1
  72. package/dist/types/src/common/graph.d.ts +2 -2
  73. package/dist/types/src/common/graph.d.ts.map +1 -1
  74. package/dist/types/src/common/index.d.ts +0 -1
  75. package/dist/types/src/common/index.d.ts.map +1 -1
  76. package/dist/types/src/common/layout.d.ts +218 -121
  77. package/dist/types/src/common/layout.d.ts.map +1 -1
  78. package/dist/types/src/common/surface.d.ts +3 -3
  79. package/dist/types/src/common/surface.d.ts.map +1 -1
  80. package/dist/types/src/common/translations.d.ts +7 -7
  81. package/dist/types/src/common/translations.d.ts.map +1 -1
  82. package/dist/types/src/core/capabilities.d.ts +6 -2
  83. package/dist/types/src/core/capabilities.d.ts.map +1 -1
  84. package/dist/types/src/core/manager.d.ts +2 -9
  85. package/dist/types/src/core/manager.d.ts.map +1 -1
  86. package/dist/types/src/core/plugin.d.ts +5 -2
  87. package/dist/types/src/core/plugin.d.ts.map +1 -1
  88. package/dist/types/src/playground/debug/Debug.d.ts +2 -3
  89. package/dist/types/src/playground/debug/Debug.d.ts.map +1 -1
  90. package/dist/types/src/playground/debug/plugin.d.ts +1 -1
  91. package/dist/types/src/playground/debug/plugin.d.ts.map +1 -1
  92. package/dist/types/src/playground/generator/Main.d.ts +2 -3
  93. package/dist/types/src/playground/generator/Main.d.ts.map +1 -1
  94. package/dist/types/src/playground/generator/Toolbar.d.ts +2 -3
  95. package/dist/types/src/playground/generator/Toolbar.d.ts.map +1 -1
  96. package/dist/types/src/playground/generator/generator.d.ts +5 -3
  97. package/dist/types/src/playground/generator/generator.d.ts.map +1 -1
  98. package/dist/types/src/playground/generator/plugin.d.ts +1 -1
  99. package/dist/types/src/playground/generator/plugin.d.ts.map +1 -1
  100. package/dist/types/src/playground/layout/Layout.d.ts +2 -2
  101. package/dist/types/src/playground/layout/Layout.d.ts.map +1 -1
  102. package/dist/types/src/playground/layout/plugin.d.ts +1 -1
  103. package/dist/types/src/playground/layout/plugin.d.ts.map +1 -1
  104. package/dist/types/src/playground/logger/Toolbar.d.ts +2 -3
  105. package/dist/types/src/playground/logger/Toolbar.d.ts.map +1 -1
  106. package/dist/types/src/playground/logger/plugin.d.ts +1 -1
  107. package/dist/types/src/playground/logger/plugin.d.ts.map +1 -1
  108. package/dist/types/src/playground/logger/schema.d.ts +1 -1
  109. package/dist/types/src/playground/logger/schema.d.ts.map +1 -1
  110. package/dist/types/src/playground/playground.stories.d.ts +2 -3
  111. package/dist/types/src/playground/playground.stories.d.ts.map +1 -1
  112. package/dist/types/src/plugin-intent/IntentPlugin.d.ts +1 -1
  113. package/dist/types/src/plugin-intent/IntentPlugin.d.ts.map +1 -1
  114. package/dist/types/src/plugin-intent/actions.d.ts +1 -1
  115. package/dist/types/src/plugin-intent/actions.d.ts.map +1 -1
  116. package/dist/types/src/plugin-intent/index.d.ts +0 -1
  117. package/dist/types/src/plugin-intent/index.d.ts.map +1 -1
  118. package/dist/types/src/plugin-intent/intent-dispatcher.d.ts +27 -20
  119. package/dist/types/src/plugin-intent/intent-dispatcher.d.ts.map +1 -1
  120. package/dist/types/src/plugin-intent/intent.d.ts +3 -3
  121. package/dist/types/src/plugin-intent/intent.d.ts.map +1 -1
  122. package/dist/types/src/plugin-settings/SettingsPlugin.d.ts +1 -1
  123. package/dist/types/src/plugin-settings/SettingsPlugin.d.ts.map +1 -1
  124. package/dist/types/src/plugin-settings/actions.d.ts +11 -1
  125. package/dist/types/src/plugin-settings/actions.d.ts.map +1 -1
  126. package/dist/types/src/plugin-settings/app-graph-builder.d.ts +197 -0
  127. package/dist/types/src/plugin-settings/app-graph-builder.d.ts.map +1 -0
  128. package/dist/types/src/plugin-settings/intent-resolver.d.ts +4 -0
  129. package/dist/types/src/plugin-settings/intent-resolver.d.ts.map +1 -0
  130. package/dist/types/src/plugin-settings/store.d.ts +5 -0
  131. package/dist/types/src/plugin-settings/store.d.ts.map +1 -0
  132. package/dist/types/src/plugin-settings/translations.d.ts +11 -0
  133. package/dist/types/src/plugin-settings/translations.d.ts.map +1 -0
  134. package/dist/types/src/react/ErrorBoundary.d.ts +1 -1
  135. package/dist/types/src/{plugin-intent → react}/IntentContext.d.ts +1 -1
  136. package/dist/types/src/react/IntentContext.d.ts.map +1 -0
  137. package/dist/types/src/react/Surface.d.ts.map +1 -1
  138. package/dist/types/src/react/Surface.stories.d.ts +15 -0
  139. package/dist/types/src/react/Surface.stories.d.ts.map +1 -0
  140. package/dist/types/src/react/common.d.ts +13 -0
  141. package/dist/types/src/react/common.d.ts.map +1 -0
  142. package/dist/types/src/react/index.d.ts +2 -0
  143. package/dist/types/src/react/index.d.ts.map +1 -1
  144. package/dist/types/src/react/useCapabilities.d.ts.map +1 -1
  145. package/dist/types/src/react/useIntentResolver.d.ts +3 -0
  146. package/dist/types/src/react/useIntentResolver.d.ts.map +1 -0
  147. package/dist/types/src/testing/withPluginManager.d.ts +6 -4
  148. package/dist/types/src/testing/withPluginManager.d.ts.map +1 -1
  149. package/dist/types/src/worker.d.ts +4 -0
  150. package/dist/types/src/worker.d.ts.map +1 -0
  151. package/package.json +29 -21
  152. package/project.json +4 -3
  153. package/src/App.tsx +17 -15
  154. package/src/common/capabilities.ts +30 -11
  155. package/src/common/events.ts +16 -2
  156. package/src/common/file.ts +1 -1
  157. package/src/common/graph.ts +2 -2
  158. package/src/common/index.ts +0 -1
  159. package/src/common/layout.ts +207 -126
  160. package/src/common/surface.ts +2 -2
  161. package/src/common/translations.ts +7 -8
  162. package/src/core/capabilities.ts +16 -7
  163. package/src/core/manager.test.ts +22 -73
  164. package/src/core/manager.ts +105 -91
  165. package/src/core/plugin.ts +6 -3
  166. package/src/playground/debug/plugin.ts +1 -1
  167. package/src/playground/generator/Toolbar.tsx +11 -11
  168. package/src/playground/generator/generator.ts +25 -0
  169. package/src/playground/generator/plugin.ts +6 -1
  170. package/src/playground/layout/plugin.ts +1 -1
  171. package/src/playground/logger/Toolbar.tsx +2 -1
  172. package/src/playground/logger/plugin.ts +7 -4
  173. package/src/playground/logger/schema.ts +1 -1
  174. package/src/plugin-intent/IntentPlugin.tsx +3 -43
  175. package/src/plugin-intent/actions.ts +1 -1
  176. package/src/plugin-intent/errors.ts +1 -1
  177. package/src/plugin-intent/index.ts +0 -1
  178. package/src/plugin-intent/intent-dispatcher.test.ts +48 -29
  179. package/src/plugin-intent/intent-dispatcher.ts +81 -42
  180. package/src/plugin-intent/intent.ts +5 -5
  181. package/src/plugin-settings/SettingsPlugin.ts +19 -13
  182. package/src/plugin-settings/actions.ts +11 -1
  183. package/src/plugin-settings/app-graph-builder.ts +122 -0
  184. package/src/plugin-settings/intent-resolver.ts +34 -0
  185. package/src/plugin-settings/store.ts +30 -0
  186. package/src/plugin-settings/translations.ts +17 -0
  187. package/src/{plugin-intent → react}/IntentContext.tsx +2 -2
  188. package/src/react/Surface.stories.tsx +96 -0
  189. package/src/react/Surface.tsx +11 -8
  190. package/src/react/common.ts +12 -0
  191. package/src/react/index.ts +2 -0
  192. package/src/react/useCapabilities.ts +1 -0
  193. package/src/react/useIntentResolver.ts +22 -0
  194. package/src/testing/withPluginManager.tsx +28 -4
  195. package/src/worker.ts +11 -0
  196. package/tsconfig.json +7 -13
  197. package/dist/lib/browser/chunk-GNLU3GAU.mjs.map +0 -7
  198. package/dist/lib/node/chunk-FBA4BB3J.cjs +0 -1639
  199. package/dist/lib/node/chunk-FBA4BB3J.cjs.map +0 -7
  200. package/dist/lib/node-esm/chunk-KPMTPXQI.mjs.map +0 -7
  201. package/dist/types/src/common/navigation.d.ts +0 -241
  202. package/dist/types/src/common/navigation.d.ts.map +0 -1
  203. package/dist/types/src/plugin-intent/IntentContext.d.ts.map +0 -1
  204. package/src/common/navigation.ts +0 -199
  205. /package/{.eslintrc.js → .eslintrc.cjs} +0 -0
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import { untracked } from '@preact/signals-core';
6
- import { Effect, Either, Match } from 'effect';
6
+ import { Array as A, Effect, Either, Match, pipe } from 'effect';
7
7
 
8
8
  import { Event } from '@dxos/async';
9
9
  import { create, type ReactiveObject } from '@dxos/live-object';
@@ -35,7 +35,6 @@ type PluginManagerState = {
35
35
  // Modules
36
36
  modules: PluginModule[];
37
37
  active: string[];
38
- pendingRemoval: string[];
39
38
 
40
39
  // Events
41
40
  eventsFired: string[];
@@ -67,7 +66,6 @@ export class PluginManager {
67
66
  enabled,
68
67
  modules: [],
69
68
  active: [],
70
- pendingRemoval: [],
71
69
  pendingReset: [],
72
70
  eventsFired: [],
73
71
  });
@@ -121,15 +119,6 @@ export class PluginManager {
121
119
  return this._state.active;
122
120
  }
123
121
 
124
- /**
125
- * Ids of modules which are pending removal.
126
- *
127
- * @reactive
128
- */
129
- get pendingRemoval(): ReactiveObject<readonly string[]> {
130
- return this._state.pendingRemoval;
131
- }
132
-
133
122
  /**
134
123
  * Ids of events which have been fired.
135
124
  *
@@ -165,8 +154,8 @@ export class PluginManager {
165
154
  * Enables a plugin.
166
155
  * @param id The id of the plugin.
167
156
  */
168
- enable(id: string): boolean {
169
- return untracked(() => {
157
+ enable(id: string): Promise<boolean> {
158
+ return untracked(async () => {
170
159
  log('enable plugin', { id });
171
160
  const plugin = this._getPlugin(id);
172
161
  if (!plugin) {
@@ -181,6 +170,15 @@ export class PluginManager {
181
170
  this._addModule(module);
182
171
  this._setPendingResetByModule(module);
183
172
  });
173
+
174
+ log('pending reset', { events: [...this.pendingReset] });
175
+ await Effect.runPromise(
176
+ Effect.all(
177
+ this.pendingReset.map((event) => this._activate(event)),
178
+ { concurrency: 'unbounded' },
179
+ ),
180
+ );
181
+
184
182
  return true;
185
183
  });
186
184
  }
@@ -206,8 +204,8 @@ export class PluginManager {
206
204
  * Disables a plugin.
207
205
  * @param id The id of the plugin.
208
206
  */
209
- disable(id: string): boolean {
210
- return untracked(() => {
207
+ disable(id: string): Promise<boolean> {
208
+ return untracked(async () => {
211
209
  log('disable plugin', { id });
212
210
  if (this._state.core.includes(id)) {
213
211
  return false;
@@ -221,16 +219,10 @@ export class PluginManager {
221
219
  const enabledIndex = this._state.enabled.findIndex((enabled) => enabled === id);
222
220
  if (enabledIndex !== -1) {
223
221
  this._state.enabled.splice(enabledIndex, 1);
222
+ await Effect.runPromise(this._deactivate(id));
224
223
 
225
224
  plugin.modules.forEach((module) => {
226
- if (this._state.active.includes(module.id)) {
227
- this._setPendingResetByModule(module);
228
- if (!this._state.pendingRemoval.includes(module.id)) {
229
- this._state.pendingRemoval.push(module.id);
230
- }
231
- } else {
232
- this._removeModule(module.id);
233
- }
225
+ this._removeModule(module.id);
234
226
  });
235
227
  }
236
228
 
@@ -328,14 +320,8 @@ export class PluginManager {
328
320
  const activationEvents = getEvents(module.activatesOn)
329
321
  .map(eventKey)
330
322
  .filter((key) => this._state.eventsFired.includes(key));
331
- const parentEvents = activationEvents.flatMap((event) => {
332
- const modules = this._getActiveModules().filter((module) =>
333
- module.activatesBefore?.map(eventKey).includes(event),
334
- );
335
- return modules.flatMap((module) => getEvents(module.activatesOn)).map(eventKey);
336
- });
337
323
 
338
- const pendingReset = Array.from(new Set([...activationEvents, ...parentEvents])).filter(
324
+ const pendingReset = Array.from(new Set(activationEvents)).filter(
339
325
  (event) => !this._state.pendingReset.includes(event),
340
326
  );
341
327
  if (pendingReset.length > 0) {
@@ -345,64 +331,91 @@ export class PluginManager {
345
331
  });
346
332
  }
347
333
 
334
+ /**
335
+ * @internal
336
+ */
348
337
  // TODO(wittjosiah): Improve error typing.
349
- private _activate(event: ActivationEvent | string): Effect.Effect<boolean, Error> {
350
- const self = this;
351
- return Effect.gen(function* () {
338
+ _activate(event: ActivationEvent | string): Effect.Effect<boolean, Error> {
339
+ return Effect.gen(this, function* () {
352
340
  const key = typeof event === 'string' ? event : eventKey(event);
353
341
  log('activating', { key });
354
- const pendingIndex = self._state.pendingReset.findIndex((event) => event === key);
342
+ const pendingIndex = this._state.pendingReset.findIndex((event) => event === key);
355
343
  if (pendingIndex !== -1) {
356
- self._state.pendingReset.splice(pendingIndex, 1);
344
+ this._state.pendingReset.splice(pendingIndex, 1);
357
345
  }
358
346
 
359
- const modules = self._getInactiveModulesByEvent(key);
347
+ const modules = this._getInactiveModulesByEvent(key).filter((module) => {
348
+ const allOf = isAllOf(module.activatesOn);
349
+ if (!allOf) {
350
+ return true;
351
+ }
352
+
353
+ const events = module.activatesOn.events.filter((event) => eventKey(event) !== key);
354
+ return events.every((event) => this._state.eventsFired.includes(eventKey(event)));
355
+ });
360
356
  if (modules.length === 0) {
361
357
  log('no modules to activate', { key });
358
+ if (!this._state.eventsFired.includes(key)) {
359
+ this._state.eventsFired.push(key);
360
+ }
362
361
  return false;
363
362
  }
364
363
 
365
- self.activation.emit({ event: key, state: 'activating' });
366
-
367
- for (const module of modules) {
368
- if (
369
- isAllOf(module.activatesOn) &&
370
- !module.activatesOn.events
371
- .filter((event) => eventKey(event) !== key)
372
- .every((event) => self._state.eventsFired.includes(eventKey(event)))
373
- ) {
374
- continue;
375
- }
364
+ log('activating modules', { key, modules: modules.map((module) => module.id) });
365
+ this.activation.emit({ event: key, state: 'activating' });
376
366
 
377
- yield* Effect.all(module.activatesBefore?.map((event) => self._activate(event)) ?? []);
367
+ // Concurrently triggers loading of lazy capabilities.
368
+ const getCapabilities = yield* Effect.all(
369
+ modules.map(({ activate }) =>
370
+ Effect.tryPromise({
371
+ try: async () => activate(this.context),
372
+ catch: (error) => error as Error,
373
+ }),
374
+ ),
375
+ { concurrency: 'unbounded' },
376
+ );
378
377
 
379
- const result = yield* self._activateModule(module).pipe(Effect.either);
380
- if (Either.isLeft(result)) {
381
- self.activation.emit({ event: key, state: 'error', error: result.left });
382
- yield* Effect.fail(result.left);
383
- }
378
+ const result = yield* pipe(
379
+ modules,
380
+ A.zip(getCapabilities),
381
+ A.map(([module, getCapabilities]) => this._activateModule(module, getCapabilities)),
382
+ // TODO(wittjosiah): This currently can't be run in parallel.
383
+ // Running this with concurrency causes races with `allOf` activation events.
384
+ Effect.all,
385
+ Effect.either,
386
+ );
384
387
 
385
- yield* Effect.all(module.activatesAfter?.map((event) => self._activate(event)) ?? []);
388
+ if (Either.isLeft(result)) {
389
+ this.activation.emit({ event: key, state: 'error', error: result.left });
390
+ yield* Effect.fail(result.left);
386
391
  }
387
392
 
388
- if (!self._state.eventsFired.includes(key)) {
389
- self._state.eventsFired.push(key);
393
+ if (!this._state.eventsFired.includes(key)) {
394
+ this._state.eventsFired.push(key);
390
395
  }
391
396
 
392
- self.activation.emit({ event: key, state: 'activated' });
397
+ this.activation.emit({ event: key, state: 'activated' });
393
398
  log('activated', { key });
394
399
 
395
400
  return true;
396
401
  });
397
402
  }
398
403
 
399
- private _activateModule(module: PluginModule): Effect.Effect<void, Error> {
400
- const self = this;
401
- return Effect.gen(function* () {
404
+ private _activateModule(
405
+ module: PluginModule,
406
+ getCapabilities: AnyCapability | AnyCapability[] | (() => Promise<AnyCapability | AnyCapability[]>),
407
+ ): Effect.Effect<void, Error> {
408
+ return Effect.gen(this, function* () {
409
+ yield* Effect.all(module.activatesBefore?.map((event) => this._activate(event)) ?? [], {
410
+ concurrency: 'unbounded',
411
+ });
412
+
413
+ log('activating module...', { module: module.id });
402
414
  // TODO(wittjosiah): This is not handling errors thrown if this is synchronous.
403
- const program = module.activate(self.context);
404
- const maybeCapabilities = yield* Match.value(program).pipe(
405
- Match.when(Effect.isEffect, (effect) => effect),
415
+ const maybeCapabilities = typeof getCapabilities === 'function' ? getCapabilities() : getCapabilities;
416
+ const resolvedCapabilities = yield* Match.value(maybeCapabilities).pipe(
417
+ // TODO(wittjosiah): Activate with an effect?
418
+ // Match.when(Effect.isEffect, (effect) => effect),
406
419
  Match.when(isPromise, (promise) =>
407
420
  Effect.tryPromise({
408
421
  try: () => promise,
@@ -411,42 +424,48 @@ export class PluginManager {
411
424
  ),
412
425
  Match.orElse((program) => Effect.succeed(program)),
413
426
  );
414
- const capabilities = Match.value(maybeCapabilities).pipe(
427
+ const capabilities = Match.value(resolvedCapabilities).pipe(
415
428
  Match.when(Array.isArray, (array) => array),
416
429
  Match.orElse((value) => [value]),
417
430
  );
418
431
  capabilities.forEach((capability) => {
419
- self.context.contributeCapability({ module: module.id, ...capability });
432
+ this.context.contributeCapability({ module: module.id, ...capability });
433
+ });
434
+ this._state.active.push(module.id);
435
+ this._capabilities.set(module.id, capabilities);
436
+ log('activated module', { module: module.id });
437
+
438
+ yield* Effect.all(module.activatesAfter?.map((event) => this._activate(event)) ?? [], {
439
+ concurrency: 'unbounded',
420
440
  });
421
- self._state.active.push(module.id);
422
- self._capabilities.set(module.id, capabilities);
423
441
  });
424
442
  }
425
443
 
426
444
  private _deactivate(id: string): Effect.Effect<boolean, Error> {
427
- const self = this;
428
- return Effect.gen(function* () {
429
- const plugin = self._getPlugin(id);
445
+ return Effect.gen(this, function* () {
446
+ const plugin = this._getPlugin(id);
430
447
  if (!plugin) {
431
448
  return false;
432
449
  }
433
450
 
434
451
  const modules = plugin.modules;
435
- const results = yield* Effect.all(modules.map((module) => self._deactivateModule(module)));
452
+ const results = yield* Effect.all(
453
+ modules.map((module) => this._deactivateModule(module)),
454
+ { concurrency: 'unbounded' },
455
+ );
436
456
  return results.every((result) => result);
437
457
  });
438
458
  }
439
459
 
440
460
  private _deactivateModule(module: PluginModule): Effect.Effect<boolean, Error> {
441
- const self = this;
442
- return Effect.gen(function* () {
461
+ return Effect.gen(this, function* () {
443
462
  const id = module.id;
444
463
  log('deactivating', { id });
445
464
 
446
- const capabilities = self._capabilities.get(id);
465
+ const capabilities = this._capabilities.get(id);
447
466
  if (capabilities) {
448
467
  for (const capability of capabilities) {
449
- self.context.removeCapability(capability.interface, capability.implementation);
468
+ this.context.removeCapability(capability.interface, capability.implementation);
450
469
  const program = capability.deactivate?.();
451
470
  yield* Match.value(program).pipe(
452
471
  Match.when(Effect.isEffect, (effect) => effect),
@@ -459,12 +478,12 @@ export class PluginManager {
459
478
  Match.orElse((program) => Effect.succeed(program)),
460
479
  );
461
480
  }
462
- self._capabilities.delete(id);
481
+ this._capabilities.delete(id);
463
482
  }
464
483
 
465
- const activeIndex = self._state.active.findIndex((event) => event === id);
484
+ const activeIndex = this._state.active.findIndex((event) => event === id);
466
485
  if (activeIndex !== -1) {
467
- self._state.active.splice(activeIndex, 1);
486
+ this._state.active.splice(activeIndex, 1);
468
487
  }
469
488
 
470
489
  log('deactivated', { id });
@@ -473,22 +492,17 @@ export class PluginManager {
473
492
  }
474
493
 
475
494
  private _reset(event: ActivationEvent | string): Effect.Effect<boolean, Error> {
476
- const self = this;
477
- return Effect.gen(function* () {
495
+ return Effect.gen(this, function* () {
478
496
  const key = typeof event === 'string' ? event : eventKey(event);
479
497
  log('reset', { key });
480
- const modules = self._getActiveModulesByEvent(key);
481
- const results = yield* Effect.all(modules.map((module) => self._deactivateModule(module)));
482
-
483
- if (self._state.pendingRemoval.length > 0) {
484
- self._state.pendingRemoval.forEach((id) => {
485
- self._removeModule(id);
486
- });
487
- self._state.pendingRemoval.splice(0, self._state.pendingRemoval.length);
488
- }
498
+ const modules = this._getActiveModulesByEvent(key);
499
+ const results = yield* Effect.all(
500
+ modules.map((module) => this._deactivateModule(module)),
501
+ { concurrency: 'unbounded' },
502
+ );
489
503
 
490
504
  if (results.every((result) => result)) {
491
- return yield* self._activate(key);
505
+ return yield* this._activate(key);
492
506
  } else {
493
507
  return false;
494
508
  }
@@ -2,8 +2,6 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type Effect } from 'effect';
6
-
7
5
  import { type MaybePromise } from '@dxos/util';
8
6
 
9
7
  import { type AnyCapability, type PluginsContext } from './capabilities';
@@ -39,7 +37,7 @@ interface PluginModuleInterface {
39
37
  */
40
38
  activate: (
41
39
  context: PluginsContext,
42
- ) => MaybePromise<AnyCapability | AnyCapability[]> | Effect.Effect<AnyCapability | AnyCapability[], Error>;
40
+ ) => MaybePromise<AnyCapability | AnyCapability[]> | Promise<() => Promise<AnyCapability | AnyCapability[]>>;
43
41
  }
44
42
 
45
43
  /**
@@ -98,6 +96,11 @@ export type PluginMeta = {
98
96
  */
99
97
  source?: string;
100
98
 
99
+ /**
100
+ * URL of screenshot.
101
+ */
102
+ screenshots?: string[];
103
+
101
104
  /**
102
105
  * Tags to help categorize the plugin.
103
106
  */
@@ -12,6 +12,6 @@ export const DebugPlugin = () =>
12
12
  defineModule({
13
13
  id: 'dxos.org/test/debug/main',
14
14
  activatesOn: Events.Startup,
15
- activate: () => Debug(),
15
+ activate: Debug,
16
16
  }),
17
17
  ]);
@@ -6,34 +6,34 @@ import React, { useCallback } from 'react';
6
6
 
7
7
  import { Button } from '@dxos/react-ui';
8
8
 
9
- import { CountEvent, createPluginId, Number } from './generator';
9
+ import { createGeneratorIntent, createPluginId, Number } from './generator';
10
10
  import { Capabilities } from '../../common';
11
11
  import { contributes } from '../../core';
12
- import { usePluginManager } from '../../react';
12
+ import { createIntent } from '../../plugin-intent';
13
+ import { useCapabilities, useIntentDispatcher, usePluginManager } from '../../react';
13
14
 
14
15
  export const Toolbar = () => {
15
16
  const manager = usePluginManager();
17
+ const { dispatchPromise: dispatch } = useIntentDispatcher();
16
18
 
17
19
  const handleAdd = useCallback(async () => {
18
20
  const id = createPluginId(Math.random().toString(16).substring(2, 8));
19
21
  await manager.add(id);
20
22
  }, [manager]);
21
23
 
22
- const handleCount = useCallback(async () => {
23
- if (manager.pendingReset.includes(CountEvent.id)) {
24
- await manager.reset(CountEvent);
25
- } else {
26
- await manager.activate(CountEvent);
27
- }
28
- }, [manager]);
24
+ const count = useCapabilities(Number).reduce((acc, curr) => acc + curr, 0);
29
25
 
30
- const count = manager.context.requestCapabilities(Number).reduce((acc, curr) => acc + curr, 0);
26
+ const generatorPlugins = manager.plugins.filter((plugin) => plugin.meta.id.startsWith('dxos.org/test/generator/'));
31
27
 
32
28
  return (
33
29
  <>
34
30
  <Button onClick={handleAdd}>Add</Button>
35
- <Button onClick={handleCount}>Count</Button>
36
31
  <div className='flex items-center'>Count: {count}</div>
32
+ {generatorPlugins.map((plugin) => (
33
+ <Button key={plugin.meta.id} onClick={() => dispatch(createIntent(createGeneratorIntent(plugin.meta.id)))}>
34
+ {plugin.meta.id.replace('dxos.org/test/generator/', '')}
35
+ </Button>
36
+ ))}
37
37
  </>
38
38
  );
39
39
  };
@@ -2,7 +2,11 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
+ import { Schema as S } from '@effect/schema';
6
+
7
+ import { Capabilities, Events } from '../../common';
5
8
  import { contributes, defineEvent, defineCapability, defineModule, definePlugin } from '../../core';
9
+ import { createResolver, type IntentSchema } from '../../plugin-intent';
6
10
 
7
11
  export const Number = defineCapability<number>('dxos.org/test/generator/number');
8
12
 
@@ -10,6 +14,15 @@ export const CountEvent = defineEvent('dxos.org/test/generator/count');
10
14
 
11
15
  export const createPluginId = (id: string) => `dxos.org/test/generator/${id}`;
12
16
 
17
+ export const createGeneratorIntent = (id: string) => {
18
+ class Alert extends S.TaggedClass<Alert>()(`${createPluginId(id)}/action/alert`, {
19
+ input: S.Void,
20
+ output: S.Void,
21
+ }) {}
22
+
23
+ return Alert as unknown as IntentSchema<any, any>;
24
+ };
25
+
13
26
  export const createNumberPlugin = (id: string) => {
14
27
  const number = Math.floor(Math.random() * 100);
15
28
 
@@ -19,5 +32,17 @@ export const createNumberPlugin = (id: string) => {
19
32
  activatesOn: CountEvent,
20
33
  activate: () => contributes(Number, number),
21
34
  }),
35
+ defineModule({
36
+ id: `${id}/intent-resolver`,
37
+ activatesOn: Events.SetupIntentResolver,
38
+ activate: () =>
39
+ contributes(
40
+ Capabilities.IntentResolver,
41
+ createResolver({
42
+ intent: createGeneratorIntent(id),
43
+ resolve: () => window.alert(JSON.stringify({ number })),
44
+ }),
45
+ ),
46
+ }),
22
47
  ]);
23
48
  };
@@ -13,6 +13,11 @@ export const GeneratorPlugin = () =>
13
13
  defineModule({
14
14
  id: 'dxos.org/test/generator/main',
15
15
  activatesOn: Events.Startup,
16
- activate: async () => [await Main(), await Toolbar()],
16
+ activate: Main,
17
+ }),
18
+ defineModule({
19
+ id: 'dxos.org/test/generator/toolbar',
20
+ activatesOn: Events.Startup,
21
+ activate: Toolbar,
17
22
  }),
18
23
  ]);
@@ -12,6 +12,6 @@ export const LayoutPlugin = () =>
12
12
  defineModule({
13
13
  id: 'dxos.org/test/layout/root',
14
14
  activatesOn: Events.Startup,
15
- activate: () => Layout(),
15
+ activate: Layout,
16
16
  }),
17
17
  ]);
@@ -9,7 +9,8 @@ import { Button } from '@dxos/react-ui';
9
9
  import { Log } from './schema';
10
10
  import { Capabilities, createSurface } from '../../common';
11
11
  import { contributes } from '../../core';
12
- import { createIntent, useIntentDispatcher } from '../../plugin-intent';
12
+ import { createIntent } from '../../plugin-intent';
13
+ import { useIntentDispatcher } from '../../react';
13
14
 
14
15
  export const Logger = () => {
15
16
  const { dispatchPromise } = useIntentDispatcher();
@@ -15,12 +15,15 @@ export const LoggerPlugin = () =>
15
15
  definePlugin({ id: 'dxos.org/test/logger' }, [
16
16
  defineModule({
17
17
  id: 'dxos.org/test/logger/intents',
18
- activatesOn: Events.SetupIntents,
18
+ activatesOn: Events.SetupIntentResolver,
19
19
  activate: () => [
20
20
  contributes(
21
21
  Capabilities.IntentResolver,
22
- createResolver(Log, ({ message }) => {
23
- log.info(message);
22
+ createResolver({
23
+ intent: Log,
24
+ resolve: ({ message }) => {
25
+ log.info(message);
26
+ },
24
27
  }),
25
28
  ),
26
29
  ],
@@ -28,6 +31,6 @@ export const LoggerPlugin = () =>
28
31
  defineModule({
29
32
  id: 'dxos.org/test/logger/surfaces',
30
33
  activatesOn: Events.Startup,
31
- activate: () => Toolbar(),
34
+ activate: Toolbar,
32
35
  }),
33
36
  ]);
@@ -2,7 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { S } from '@dxos/echo-schema';
5
+ import { Schema as S } from '@effect/schema';
6
6
 
7
7
  export class Log extends S.TaggedClass<Log>()('dxos.org/test/logger/log', {
8
8
  input: S.Struct({
@@ -2,19 +2,9 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Effect } from 'effect';
6
- import React from 'react';
7
-
8
- import { create } from '@dxos/live-object';
9
-
10
- import { IntentProvider } from './IntentContext';
11
5
  import { INTENT_PLUGIN } from './actions';
12
- import { type AnyIntentResolver, createDispatcher, type IntentContext } from './intent-dispatcher';
13
- import { Capabilities, Events } from '../common';
14
- import { contributes, defineModule, definePlugin } from '../core';
15
-
16
- const defaultEffect = () => Effect.fail(new Error('Intent runtime not ready'));
17
- const defaultPromise = () => Effect.runPromise(defaultEffect());
6
+ import { Events } from '../common';
7
+ import { defineModule, definePlugin, lazy } from '../core';
18
8
 
19
9
  export const IntentPlugin = () =>
20
10
  definePlugin({ id: INTENT_PLUGIN }, [
@@ -24,37 +14,7 @@ export const IntentPlugin = () =>
24
14
  // This is fine for now because it's how it worked prior to capabilities api anyways.
25
15
  // In the future, the intent dispatcher should be able to be reset without resetting the entire app.
26
16
  activatesOn: Events.Startup,
27
- activatesBefore: [Events.SetupIntents],
28
17
  activatesAfter: [Events.DispatcherReady],
29
- activate: (context) => {
30
- const state = create<IntentContext>({
31
- dispatch: defaultEffect,
32
- dispatchPromise: defaultPromise,
33
- undo: defaultEffect,
34
- undoPromise: defaultPromise,
35
- });
36
-
37
- // TODO(wittjosiah): Make getResolver callback async and allow resolvers to be requested on demand.
38
- const { dispatch, dispatchPromise, undo, undoPromise } = createDispatcher((module) =>
39
- context
40
- .requestCapabilities(Capabilities.IntentResolver, (c, moduleId): c is AnyIntentResolver => {
41
- return module ? moduleId === module : true;
42
- })
43
- .flat(),
44
- );
45
-
46
- state.dispatch = dispatch;
47
- state.dispatchPromise = dispatchPromise;
48
- state.undo = undo;
49
- state.undoPromise = undoPromise;
50
-
51
- return [
52
- contributes(Capabilities.IntentDispatcher, state),
53
- contributes(Capabilities.ReactContext, {
54
- id: INTENT_PLUGIN,
55
- context: ({ children }) => <IntentProvider value={state}>{children}</IntentProvider>,
56
- }),
57
- ];
58
- },
18
+ activate: lazy(() => import('./intent-dispatcher')),
59
19
  }),
60
20
  ]);
@@ -2,7 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { S } from '@dxos/echo-schema';
5
+ import { Schema as S } from '@effect/schema';
6
6
 
7
7
  import { Label } from './intent';
8
8
 
@@ -15,7 +15,7 @@ export class BaseError extends Error {
15
15
  readonly context?: Record<string, any>,
16
16
  ) {
17
17
  // TODO(dmaretskyi): Error.cause.
18
- super(message ?? code);
18
+ super(message ?? code, { cause: context });
19
19
  this.name = code;
20
20
  // NOTE: Restores prototype chain (https://stackoverflow.com/a/48342359).
21
21
  Object.setPrototypeOf(this, new.target.prototype);
@@ -5,5 +5,4 @@
5
5
  export * from './actions';
6
6
  export * from './intent';
7
7
  export * from './intent-dispatcher';
8
- export * from './IntentContext';
9
8
  export * from './IntentPlugin';