@dxos/app-framework 0.8.4-main.fd6878d → 0.8.4-main.fffef41

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 (209) hide show
  1. package/.storybook/main.mts +11 -0
  2. package/.storybook/preview.mts +8 -0
  3. package/.swc/plugins/linux_x86_64_19.0.0/727453fb3a62f7f1d952a41e051ca8a6f88cadc45cee43c6a4d1aa45f9b75665.wasmer-v7 +0 -0
  4. package/.swc/plugins/{v7_linux_x86_64_13.0.0/fce1bdb8e20a094e4af08bad09cc81497ed0e2e7c51223b07d371063cca18429 → linux_x86_64_19.0.0/fce1bdb8e20a094e4af08bad09cc81497ed0e2e7c51223b07d371063cca18429.wasmer-v7} +0 -0
  5. package/dist/lib/browser/{app-graph-builder-MOVKFH3J.mjs → app-graph-builder-OIEZZC45.mjs} +31 -30
  6. package/dist/lib/browser/app-graph-builder-OIEZZC45.mjs.map +7 -0
  7. package/dist/lib/browser/{chunk-OSBZFKMO.mjs → chunk-6XKO24JP.mjs} +232 -177
  8. package/dist/lib/browser/chunk-6XKO24JP.mjs.map +7 -0
  9. package/dist/lib/browser/{chunk-ORWHM7CO.mjs → chunk-SCPE4ZO2.mjs} +11 -8
  10. package/dist/lib/browser/chunk-SCPE4ZO2.mjs.map +7 -0
  11. package/dist/lib/browser/{chunk-NKCIDYDI.mjs → chunk-WPW5VVAX.mjs} +189 -136
  12. package/dist/lib/browser/chunk-WPW5VVAX.mjs.map +7 -0
  13. package/dist/lib/browser/index.mjs +20 -56
  14. package/dist/lib/browser/index.mjs.map +3 -3
  15. package/dist/lib/browser/{intent-dispatcher-FTTJLVGN.mjs → intent-dispatcher-LZ4AE66E.mjs} +2 -2
  16. package/dist/lib/browser/{intent-resolver-ZCGEAG3E.mjs → intent-resolver-QVCKRX6G.mjs} +7 -7
  17. package/dist/lib/browser/intent-resolver-QVCKRX6G.mjs.map +7 -0
  18. package/dist/lib/browser/meta.json +1 -1
  19. package/dist/lib/browser/react/index.mjs +34 -0
  20. package/dist/lib/browser/{store-3QB6Q2BC.mjs → store-CNPHOYTJ.mjs} +5 -5
  21. package/dist/lib/browser/store-CNPHOYTJ.mjs.map +7 -0
  22. package/dist/lib/browser/testing/index.mjs +17 -21
  23. package/dist/lib/browser/testing/index.mjs.map +3 -3
  24. package/dist/lib/node-esm/{app-graph-builder-ODE4B5GT.mjs → app-graph-builder-EBU4NVWD.mjs} +31 -30
  25. package/dist/lib/node-esm/app-graph-builder-EBU4NVWD.mjs.map +7 -0
  26. package/dist/lib/node-esm/{chunk-WU3QN5B6.mjs → chunk-3UPX5OIS.mjs} +232 -177
  27. package/dist/lib/node-esm/chunk-3UPX5OIS.mjs.map +7 -0
  28. package/dist/lib/node-esm/{chunk-YEN7NKTF.mjs → chunk-XJZGUJ3H.mjs} +189 -136
  29. package/dist/lib/node-esm/chunk-XJZGUJ3H.mjs.map +7 -0
  30. package/dist/lib/node-esm/{chunk-UMZQERLE.mjs → chunk-ZX63QUGE.mjs} +11 -8
  31. package/dist/lib/node-esm/chunk-ZX63QUGE.mjs.map +7 -0
  32. package/dist/lib/node-esm/index.mjs +20 -56
  33. package/dist/lib/node-esm/index.mjs.map +3 -3
  34. package/dist/lib/node-esm/{intent-dispatcher-YQIQ55LJ.mjs → intent-dispatcher-MGOJ3CHD.mjs} +2 -2
  35. package/dist/lib/node-esm/{intent-resolver-KG27L7EQ.mjs → intent-resolver-URF3HN3G.mjs} +7 -7
  36. package/dist/lib/node-esm/intent-resolver-URF3HN3G.mjs.map +7 -0
  37. package/dist/lib/node-esm/meta.json +1 -1
  38. package/dist/lib/node-esm/react/index.mjs +35 -0
  39. package/dist/lib/node-esm/{store-TIJAVO3D.mjs → store-RK5B4XEL.mjs} +5 -5
  40. package/dist/lib/node-esm/store-RK5B4XEL.mjs.map +7 -0
  41. package/dist/lib/node-esm/testing/index.mjs +17 -21
  42. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  43. package/dist/types/src/common/capabilities.d.ts +41 -43
  44. package/dist/types/src/common/capabilities.d.ts.map +1 -1
  45. package/dist/types/src/common/collaboration.d.ts +10 -9
  46. package/dist/types/src/common/collaboration.d.ts.map +1 -1
  47. package/dist/types/src/common/file.d.ts +1 -1
  48. package/dist/types/src/common/file.d.ts.map +1 -1
  49. package/dist/types/src/common/layout.d.ts +1 -3
  50. package/dist/types/src/common/layout.d.ts.map +1 -1
  51. package/dist/types/src/common/surface.d.ts +19 -16
  52. package/dist/types/src/common/surface.d.ts.map +1 -1
  53. package/dist/types/src/common/translations.d.ts +1 -1
  54. package/dist/types/src/common/translations.d.ts.map +1 -1
  55. package/dist/types/src/core/capabilities.d.ts +19 -16
  56. package/dist/types/src/core/capabilities.d.ts.map +1 -1
  57. package/dist/types/src/core/manager.d.ts +1 -1
  58. package/dist/types/src/core/manager.d.ts.map +1 -1
  59. package/dist/types/src/core/plugin.d.ts +8 -1
  60. package/dist/types/src/core/plugin.d.ts.map +1 -1
  61. package/dist/types/src/index.d.ts +0 -2
  62. package/dist/types/src/index.d.ts.map +1 -1
  63. package/dist/types/src/playground/debug/Debug.d.ts +1 -1
  64. package/dist/types/src/playground/debug/plugin.d.ts +1 -1
  65. package/dist/types/src/playground/debug/plugin.d.ts.map +1 -1
  66. package/dist/types/src/playground/generator/Main.d.ts +1 -1
  67. package/dist/types/src/playground/generator/Main.d.ts.map +1 -1
  68. package/dist/types/src/playground/generator/Toolbar.d.ts +1 -1
  69. package/dist/types/src/playground/generator/generator.d.ts +1 -1
  70. package/dist/types/src/playground/generator/generator.d.ts.map +1 -1
  71. package/dist/types/src/playground/generator/plugin.d.ts +1 -1
  72. package/dist/types/src/playground/generator/plugin.d.ts.map +1 -1
  73. package/dist/types/src/playground/layout/Layout.d.ts +2 -2
  74. package/dist/types/src/playground/layout/plugin.d.ts +1 -1
  75. package/dist/types/src/playground/layout/plugin.d.ts.map +1 -1
  76. package/dist/types/src/playground/logger/Toolbar.d.ts +1 -1
  77. package/dist/types/src/playground/logger/plugin.d.ts +1 -1
  78. package/dist/types/src/playground/logger/plugin.d.ts.map +1 -1
  79. package/dist/types/src/playground/logger/schema.d.ts +1 -1
  80. package/dist/types/src/playground/logger/schema.d.ts.map +1 -1
  81. package/dist/types/src/playground/playground.stories.d.ts +5 -4
  82. package/dist/types/src/playground/playground.stories.d.ts.map +1 -1
  83. package/dist/types/src/plugin-intent/IntentPlugin.d.ts +1 -1
  84. package/dist/types/src/plugin-intent/IntentPlugin.d.ts.map +1 -1
  85. package/dist/types/src/plugin-intent/actions.d.ts +5 -7
  86. package/dist/types/src/plugin-intent/actions.d.ts.map +1 -1
  87. package/dist/types/src/plugin-intent/errors.d.ts.map +1 -1
  88. package/dist/types/src/plugin-intent/index.d.ts +1 -0
  89. package/dist/types/src/plugin-intent/index.d.ts.map +1 -1
  90. package/dist/types/src/plugin-intent/intent-dispatcher.d.ts +5 -5
  91. package/dist/types/src/plugin-intent/intent-dispatcher.d.ts.map +1 -1
  92. package/dist/types/src/plugin-intent/intent.d.ts +1 -1
  93. package/dist/types/src/plugin-intent/intent.d.ts.map +1 -1
  94. package/dist/types/src/plugin-intent/meta.d.ts +3 -0
  95. package/dist/types/src/plugin-intent/meta.d.ts.map +1 -0
  96. package/dist/types/src/plugin-settings/SettingsPlugin.d.ts +1 -1
  97. package/dist/types/src/plugin-settings/SettingsPlugin.d.ts.map +1 -1
  98. package/dist/types/src/plugin-settings/actions.d.ts +5 -7
  99. package/dist/types/src/plugin-settings/actions.d.ts.map +1 -1
  100. package/dist/types/src/plugin-settings/app-graph-builder.d.ts +1 -1
  101. package/dist/types/src/plugin-settings/app-graph-builder.d.ts.map +1 -1
  102. package/dist/types/src/plugin-settings/intent-resolver.d.ts +1 -1
  103. package/dist/types/src/plugin-settings/meta.d.ts +3 -0
  104. package/dist/types/src/plugin-settings/meta.d.ts.map +1 -0
  105. package/dist/types/src/plugin-settings/store.d.ts +1 -1
  106. package/dist/types/src/plugin-settings/translations.d.ts +2 -1
  107. package/dist/types/src/plugin-settings/translations.d.ts.map +1 -1
  108. package/dist/types/src/react/App.d.ts +10 -0
  109. package/dist/types/src/react/App.d.ts.map +1 -0
  110. package/dist/types/src/react/App.stories.d.ts +14 -0
  111. package/dist/types/src/react/App.stories.d.ts.map +1 -0
  112. package/dist/types/src/react/DefaultFallback.d.ts +8 -0
  113. package/dist/types/src/react/DefaultFallback.d.ts.map +1 -0
  114. package/dist/types/src/react/ErrorBoundary.d.ts +2 -2
  115. package/dist/types/src/react/ErrorBoundary.d.ts.map +1 -1
  116. package/dist/types/src/react/Surface.d.ts +5 -5
  117. package/dist/types/src/react/Surface.d.ts.map +1 -1
  118. package/dist/types/src/react/Surface.stories.d.ts +8 -10
  119. package/dist/types/src/react/Surface.stories.d.ts.map +1 -1
  120. package/dist/types/src/react/index.d.ts +2 -0
  121. package/dist/types/src/react/index.d.ts.map +1 -1
  122. package/dist/types/src/react/types.d.ts +14 -0
  123. package/dist/types/src/react/types.d.ts.map +1 -0
  124. package/dist/types/src/{App.d.ts → react/useApp.d.ts} +7 -6
  125. package/dist/types/src/react/useApp.d.ts.map +1 -0
  126. package/dist/types/src/react/useLoading.d.ts +19 -0
  127. package/dist/types/src/react/useLoading.d.ts.map +1 -0
  128. package/dist/types/src/testing/withPluginManager.d.ts +7 -8
  129. package/dist/types/src/testing/withPluginManager.d.ts.map +1 -1
  130. package/dist/types/src/testing/withPluginManager.stories.d.ts +9 -3
  131. package/dist/types/src/testing/withPluginManager.stories.d.ts.map +1 -1
  132. package/dist/types/tsconfig.tsbuildinfo +1 -1
  133. package/moon.yml +5 -1
  134. package/package.json +44 -40
  135. package/src/common/capabilities.ts +33 -25
  136. package/src/common/collaboration.ts +6 -9
  137. package/src/common/file.ts +1 -1
  138. package/src/common/layout.ts +3 -4
  139. package/src/common/surface.ts +23 -21
  140. package/src/common/translations.ts +1 -1
  141. package/src/core/capabilities.test.ts +2 -2
  142. package/src/core/capabilities.ts +35 -27
  143. package/src/core/manager.test.ts +19 -19
  144. package/src/core/manager.ts +14 -7
  145. package/src/core/plugin.ts +13 -2
  146. package/src/index.ts +0 -2
  147. package/src/playground/debug/Debug.tsx +1 -1
  148. package/src/playground/debug/plugin.ts +7 -8
  149. package/src/playground/generator/Main.tsx +0 -1
  150. package/src/playground/generator/generator.ts +2 -2
  151. package/src/playground/generator/plugin.ts +12 -13
  152. package/src/playground/layout/plugin.ts +9 -8
  153. package/src/playground/logger/plugin.ts +27 -23
  154. package/src/playground/logger/schema.ts +1 -1
  155. package/src/playground/playground.stories.tsx +17 -14
  156. package/src/plugin-intent/IntentPlugin.ts +12 -13
  157. package/src/plugin-intent/actions.ts +4 -6
  158. package/src/plugin-intent/errors.ts +2 -1
  159. package/src/plugin-intent/index.ts +1 -0
  160. package/src/plugin-intent/intent-dispatcher.test.ts +10 -3
  161. package/src/plugin-intent/intent-dispatcher.ts +16 -8
  162. package/src/plugin-intent/intent.ts +1 -1
  163. package/src/plugin-intent/meta.ts +10 -0
  164. package/src/plugin-settings/SettingsPlugin.ts +25 -27
  165. package/src/plugin-settings/actions.ts +9 -13
  166. package/src/plugin-settings/app-graph-builder.ts +22 -20
  167. package/src/plugin-settings/intent-resolver.ts +2 -2
  168. package/src/plugin-settings/meta.ts +10 -0
  169. package/src/plugin-settings/store.ts +2 -2
  170. package/src/plugin-settings/translations.ts +4 -4
  171. package/src/react/App.stories.tsx +33 -0
  172. package/src/react/App.tsx +59 -0
  173. package/src/react/DefaultFallback.tsx +26 -0
  174. package/src/react/ErrorBoundary.tsx +10 -8
  175. package/src/react/Surface.stories.tsx +79 -51
  176. package/src/react/Surface.tsx +67 -36
  177. package/src/react/index.ts +4 -0
  178. package/src/react/types.ts +38 -0
  179. package/src/react/useApp.tsx +165 -0
  180. package/src/react/useCapabilities.ts +2 -2
  181. package/src/react/useLoading.tsx +70 -0
  182. package/src/testing/withPluginManager.stories.tsx +7 -4
  183. package/src/testing/withPluginManager.tsx +27 -29
  184. package/tsconfig.json +11 -9
  185. package/vitest.config.ts +8 -6
  186. package/.swc/plugins/v7_linux_x86_64_13.0.0/c614d7475354583212fbd7669acbae95b9832c305bf51bdaabe2e6de05abb6bf +0 -0
  187. package/dist/lib/browser/app-graph-builder-MOVKFH3J.mjs.map +0 -7
  188. package/dist/lib/browser/chunk-NKCIDYDI.mjs.map +0 -7
  189. package/dist/lib/browser/chunk-ORWHM7CO.mjs.map +0 -7
  190. package/dist/lib/browser/chunk-OSBZFKMO.mjs.map +0 -7
  191. package/dist/lib/browser/intent-resolver-ZCGEAG3E.mjs.map +0 -7
  192. package/dist/lib/browser/store-3QB6Q2BC.mjs.map +0 -7
  193. package/dist/lib/browser/worker.mjs +0 -79
  194. package/dist/lib/node-esm/app-graph-builder-ODE4B5GT.mjs.map +0 -7
  195. package/dist/lib/node-esm/chunk-UMZQERLE.mjs.map +0 -7
  196. package/dist/lib/node-esm/chunk-WU3QN5B6.mjs.map +0 -7
  197. package/dist/lib/node-esm/chunk-YEN7NKTF.mjs.map +0 -7
  198. package/dist/lib/node-esm/intent-resolver-KG27L7EQ.mjs.map +0 -7
  199. package/dist/lib/node-esm/store-TIJAVO3D.mjs.map +0 -7
  200. package/dist/lib/node-esm/worker.mjs +0 -80
  201. package/dist/types/src/App.d.ts.map +0 -1
  202. package/dist/types/src/worker.d.ts +0 -4
  203. package/dist/types/src/worker.d.ts.map +0 -1
  204. package/src/App.tsx +0 -284
  205. package/src/worker.ts +0 -11
  206. /package/dist/lib/browser/{intent-dispatcher-FTTJLVGN.mjs.map → intent-dispatcher-LZ4AE66E.mjs.map} +0 -0
  207. /package/dist/lib/browser/{worker.mjs.map → react/index.mjs.map} +0 -0
  208. /package/dist/lib/node-esm/{intent-dispatcher-YQIQ55LJ.mjs.map → intent-dispatcher-MGOJ3CHD.mjs.map} +0 -0
  209. /package/dist/lib/node-esm/{worker.mjs.map → react/index.mjs.map} +0 -0
@@ -2,8 +2,11 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Effect, Option, Ref, pipe } from 'effect';
6
- import { type Simplify } from 'effect/Types';
5
+ import * as Effect from 'effect/Effect';
6
+ import * as Function from 'effect/Function';
7
+ import * as Option from 'effect/Option';
8
+ import * as Ref from 'effect/Ref';
9
+ import type * as Types from 'effect/Types';
7
10
 
8
11
  import { live } from '@dxos/live-object';
9
12
  import { log } from '@dxos/log';
@@ -132,7 +135,7 @@ export const createResolver = <Tag extends string, Fields extends IntentParams,
132
135
  */
133
136
  export type PromiseIntentDispatcher = <Fields extends IntentParams>(
134
137
  intent: IntentChain<any, any, any, Fields>,
135
- ) => Promise<Simplify<IntentDispatcherResult<IntentData<Fields>, IntentResultData<Fields>>>>;
138
+ ) => Promise<Types.Simplify<IntentDispatcherResult<IntentData<Fields>, IntentResultData<Fields>>>>;
136
139
 
137
140
  /**
138
141
  * Creates an effect for intents.
@@ -141,7 +144,7 @@ export type IntentDispatcher = <Fields extends IntentParams>(
141
144
  intent: IntentChain<any, any, any, Fields>,
142
145
  depth?: number,
143
146
  ) => Effect.Effect<
144
- Simplify<Required<IntentDispatcherResult<IntentData<Fields>, IntentResultData<Fields>>>['data']>,
147
+ Types.Simplify<Required<IntentDispatcherResult<IntentData<Fields>, IntentResultData<Fields>>>['data']>,
145
148
  Error
146
149
  >;
147
150
 
@@ -197,7 +200,7 @@ export const createDispatcher = (
197
200
  .filter((resolver) => !resolver.filter || resolver.filter(intent.data))
198
201
  .toSorted(byPosition);
199
202
  if (candidates.length === 0) {
200
- yield* Effect.fail(new NoResolversError(intent.id));
203
+ return yield* Effect.fail(new NoResolversError(intent.id));
201
204
  }
202
205
 
203
206
  const effect = candidates[0].resolve(intent.data, intent.undo ?? false);
@@ -206,15 +209,18 @@ export const createDispatcher = (
206
209
  });
207
210
 
208
211
  const dispatch: IntentDispatcher = (intentChain, depth = 0) => {
212
+ log('dispatch', { intentChain: intentChain.all.map((i) => i.id), depth });
209
213
  return Effect.gen(function* () {
210
214
  if (depth > executionLimit) {
211
- yield* Effect.fail(new CycleDetectedError());
215
+ return yield* Effect.fail(new CycleDetectedError());
212
216
  }
213
217
 
214
218
  const resultsRef = yield* Ref.make<AnyIntentResult[]>([]);
215
219
  for (const intent of intentChain.all) {
220
+ log('processing', { intent });
216
221
  const { data: prev } = (yield* resultsRef.get)[0] ?? {};
217
222
  const result = yield* handleIntent({ ...intent, data: { ...intent.data, ...prev } });
223
+ log('ok', { intent: intent.id, result });
218
224
  yield* Ref.update(resultsRef, (results) => [result, ...results]);
219
225
  if (result.intents) {
220
226
  for (const intent of result.intents) {
@@ -231,7 +237,8 @@ export const createDispatcher = (
231
237
  // error: result.error.message,
232
238
  // }),
233
239
  // );
234
- yield* Effect.fail(result.error);
240
+ log.error('failed', { intent: intent.id, error: result.error });
241
+ return yield* Effect.fail(result.error);
235
242
  }
236
243
  }
237
244
 
@@ -252,7 +259,7 @@ export const createDispatcher = (
252
259
 
253
260
  if (result.undoable && isUndoable(results)) {
254
261
  // TODO(wittjosiah): Is there a better way to handle showing undo for chains?
255
- yield* pipe(
262
+ yield* Function.pipe(
256
263
  dispatch(createIntent(IntentAction.ShowUndo, { message: result.undoable.message })),
257
264
  Effect.catchSome((err) =>
258
265
  err instanceof NoResolversError ? Option.some(Effect.succeed(undefined)) : Option.none(),
@@ -260,6 +267,7 @@ export const createDispatcher = (
260
267
  );
261
268
  }
262
269
 
270
+ log('done', { intent: intentChain.all.map((i) => i.id), result: result.data });
263
271
  return result.data;
264
272
  });
265
273
  };
@@ -2,7 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
5
+ import * as Schema from 'effect/Schema';
6
6
 
7
7
  export type IntentParams = {
8
8
  readonly input: Schema.Schema.All;
@@ -0,0 +1,10 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type PluginMeta } from '@dxos/app-framework';
6
+
7
+ export const meta: PluginMeta = {
8
+ id: 'dxos.org/plugin/intent',
9
+ name: 'Intent',
10
+ };
@@ -5,32 +5,30 @@
5
5
  import { Capabilities, Events } from '../common';
6
6
  import { contributes, defineModule, definePlugin, lazy } from '../core';
7
7
 
8
- import { SETTINGS_PLUGIN } from './actions';
8
+ import { meta } from './meta';
9
9
  import { translations } from './translations';
10
10
 
11
- // TODO(wittjosiah): Add options to exclude some modules.
12
- export const SettingsPlugin = () =>
13
- definePlugin({ id: SETTINGS_PLUGIN, name: 'Settings' }, [
14
- defineModule({
15
- id: `${SETTINGS_PLUGIN}/module/store`,
16
- activatesOn: Events.Startup,
17
- activatesBefore: [Events.SetupSettings],
18
- activatesAfter: [Events.SettingsReady],
19
- activate: lazy(() => import('./store')),
20
- }),
21
- defineModule({
22
- id: `${SETTINGS_PLUGIN}/module/translations`,
23
- activatesOn: Events.SetupTranslations,
24
- activate: () => contributes(Capabilities.Translations, translations),
25
- }),
26
- defineModule({
27
- id: `${SETTINGS_PLUGIN}/module/intent-resolver`,
28
- activatesOn: Events.SetupIntentResolver,
29
- activate: lazy(() => import('./intent-resolver')),
30
- }),
31
- defineModule({
32
- id: `${SETTINGS_PLUGIN}/module/app-graph-builder`,
33
- activatesOn: Events.SetupAppGraph,
34
- activate: lazy(() => import('./app-graph-builder')),
35
- }),
36
- ]);
11
+ export const SettingsPlugin = definePlugin(meta, () => [
12
+ defineModule({
13
+ id: `${meta.id}/module/store`,
14
+ activatesOn: Events.Startup,
15
+ activatesBefore: [Events.SetupSettings],
16
+ activatesAfter: [Events.SettingsReady],
17
+ activate: lazy(() => import('./store')),
18
+ }),
19
+ defineModule({
20
+ id: `${meta.id}/module/translations`,
21
+ activatesOn: Events.SetupTranslations,
22
+ activate: () => contributes(Capabilities.Translations, translations),
23
+ }),
24
+ defineModule({
25
+ id: `${meta.id}/module/intent-resolver`,
26
+ activatesOn: Events.SetupIntentResolver,
27
+ activate: lazy(() => import('./intent-resolver')),
28
+ }),
29
+ defineModule({
30
+ id: `${meta.id}/module/app-graph-builder`,
31
+ activatesOn: Events.SetupAppGraph,
32
+ activate: lazy(() => import('./app-graph-builder')),
33
+ }),
34
+ ]);
@@ -2,28 +2,24 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
5
+ import * as Schema from 'effect/Schema';
6
6
 
7
- export const SETTINGS_PLUGIN = 'dxos.org/plugin/settings';
8
- export const SETTINGS_ACTION = `${SETTINGS_PLUGIN}/action`;
9
- // TODO(wittjosiah): This is a hack to prevent the previous deck from being set for pinned items.
10
- // Ideally this should be worked into the data model in a generic way.
7
+ import { meta } from './meta';
8
+
9
+ // TODO(burdon): Document.
11
10
  export const SETTINGS_ID = '!dxos:settings';
12
11
  export const SETTINGS_KEY = 'settings';
13
12
 
14
13
  export namespace SettingsAction {
15
- export class Open extends Schema.TaggedClass<Open>()(`${SETTINGS_ACTION}/open`, {
14
+ export class Open extends Schema.TaggedClass<Open>()(`${meta.id}/open`, {
16
15
  input: Schema.Struct({
17
16
  plugin: Schema.optional(Schema.String),
18
17
  }),
19
18
  output: Schema.Void,
20
19
  }) {}
21
20
 
22
- export class OpenPluginRegistry extends Schema.TaggedClass<OpenPluginRegistry>()(
23
- `${SETTINGS_ACTION}/open-plugin-registry`,
24
- {
25
- input: Schema.Void,
26
- output: Schema.Void,
27
- },
28
- ) {}
21
+ export class OpenPluginRegistry extends Schema.TaggedClass<OpenPluginRegistry>()(`${meta.id}/open-plugin-registry`, {
22
+ input: Schema.Void,
23
+ output: Schema.Void,
24
+ }) {}
29
25
  }
@@ -2,8 +2,9 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Rx } from '@effect-rx/rx-react';
6
- import { Option, pipe } from 'effect';
5
+ import { Atom } from '@effect-atom/atom-react';
6
+ import * as Function from 'effect/Function';
7
+ import * as Option from 'effect/Option';
7
8
 
8
9
  import { ROOT_ID, createExtension } from '@dxos/app-graph';
9
10
  import { type SettingsStore, type SettingsValue } from '@dxos/local-storage';
@@ -13,26 +14,27 @@ import { Capabilities } from '../common';
13
14
  import { type PluginContext, type PluginMeta, contributes } from '../core';
14
15
  import { createIntent } from '../plugin-intent';
15
16
 
16
- import { SETTINGS_ID, SETTINGS_KEY, SETTINGS_PLUGIN, SettingsAction } from './actions';
17
+ import { SETTINGS_ID, SETTINGS_KEY, SettingsAction } from './actions';
18
+ import { meta } from './meta';
17
19
 
18
20
  export default (context: PluginContext) =>
19
21
  contributes(Capabilities.AppGraphBuilder, [
20
22
  createExtension({
21
- id: `${SETTINGS_PLUGIN}/action`,
23
+ id: `${meta.id}/action`,
22
24
  actions: (node) =>
23
- Rx.make((get) =>
24
- pipe(
25
+ Atom.make((get) =>
26
+ Function.pipe(
25
27
  get(node),
26
28
  Option.flatMap((node) => (node.id === ROOT_ID ? Option.some(node) : Option.none())),
27
29
  Option.map(() => [
28
30
  {
29
- id: SETTINGS_PLUGIN,
31
+ id: meta.id,
30
32
  data: async () => {
31
33
  const { dispatchPromise: dispatch } = context.getCapability(Capabilities.IntentDispatcher);
32
34
  await dispatch(createIntent(SettingsAction.Open));
33
35
  },
34
36
  properties: {
35
- label: ['open settings label', { ns: SETTINGS_PLUGIN }],
37
+ label: ['open settings label', { ns: meta.id }],
36
38
  icon: 'ph--gear--regular',
37
39
  disposition: 'menu',
38
40
  keyBinding: {
@@ -47,18 +49,18 @@ export default (context: PluginContext) =>
47
49
  ),
48
50
  }),
49
51
  createExtension({
50
- id: `${SETTINGS_PLUGIN}/core`,
52
+ id: `${meta.id}/core`,
51
53
  connector: (node) =>
52
- Rx.make((get) =>
53
- pipe(
54
+ Atom.make((get) =>
55
+ Function.pipe(
54
56
  get(node),
55
57
  Option.flatMap((node) => (node.id === ROOT_ID ? Option.some(node) : Option.none())),
56
58
  Option.map(() => [
57
59
  {
58
60
  id: SETTINGS_ID,
59
- type: SETTINGS_PLUGIN,
61
+ type: meta.id,
60
62
  properties: {
61
- label: ['app settings label', { ns: SETTINGS_PLUGIN }],
63
+ label: ['app settings label', { ns: meta.id }],
62
64
  icon: 'ph--gear--regular',
63
65
  disposition: 'pin-end',
64
66
  position: 'hoist',
@@ -71,10 +73,10 @@ export default (context: PluginContext) =>
71
73
  ),
72
74
  }),
73
75
  createExtension({
74
- id: `${SETTINGS_PLUGIN}/core-plugins`,
76
+ id: `${meta.id}/core-plugins`,
75
77
  connector: (node) =>
76
- Rx.make((get) =>
77
- pipe(
78
+ Atom.make((get) =>
79
+ Function.pipe(
78
80
  get(node),
79
81
  Option.flatMap((node) => (node.id !== SETTINGS_ID ? Option.none() : Option.some(node))),
80
82
  Option.map(() => {
@@ -106,7 +108,7 @@ export default (context: PluginContext) =>
106
108
  id: `${SETTINGS_KEY}:custom-plugins`,
107
109
  type: 'category',
108
110
  properties: {
109
- label: ['custom plugins label', { ns: SETTINGS_PLUGIN }],
111
+ label: ['custom plugins label', { ns: meta.id }],
110
112
  icon: 'ph--squares-four--regular',
111
113
  role: 'branch',
112
114
  disposition: 'collection',
@@ -119,10 +121,10 @@ export default (context: PluginContext) =>
119
121
  ),
120
122
  }),
121
123
  createExtension({
122
- id: `${SETTINGS_PLUGIN}/custom-plugins`,
124
+ id: `${meta.id}/custom-plugins`,
123
125
  connector: (node) =>
124
- Rx.make((get) =>
125
- pipe(
126
+ Atom.make((get) =>
127
+ Function.pipe(
126
128
  get(node),
127
129
  Option.flatMap((node) =>
128
130
  node.id !== `${SETTINGS_KEY}:custom-plugins` ? Option.none() : Option.some(node),
@@ -2,7 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { pipe } from 'effect';
5
+ import * as Function from 'effect/Function';
6
6
 
7
7
  import { Capabilities, LayoutAction } from '../common';
8
8
  import { contributes } from '../core';
@@ -20,7 +20,7 @@ export default () =>
20
20
  return {
21
21
  intents: [
22
22
  plugin
23
- ? pipe(
23
+ ? Function.pipe(
24
24
  openSettings,
25
25
  chain(LayoutAction.Open, {
26
26
  part: 'main',
@@ -0,0 +1,10 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type PluginMeta } from '@dxos/app-framework';
6
+
7
+ export const meta: PluginMeta = {
8
+ id: 'dxos.org/plugin/settings',
9
+ name: 'Settings',
10
+ };
@@ -8,11 +8,11 @@ import { Capabilities } from '../common';
8
8
  import { type PluginContext, contributes } from '../core';
9
9
 
10
10
  export default (context: PluginContext) => {
11
- // TODO(wittjosiah): Replace with rx?
11
+ // TODO(wittjosiah): Replace with atom?
12
12
  const settingsStore = new RootSettingsStore();
13
13
 
14
14
  let previous: Capabilities.Settings[] = [];
15
- const registry = context.getCapability(Capabilities.RxRegistry);
15
+ const registry = context.getCapability(Capabilities.AtomRegistry);
16
16
  const cancel = registry.subscribe(
17
17
  context.capabilities(Capabilities.Settings),
18
18
  (allSettings) => {
@@ -1,15 +1,15 @@
1
1
  //
2
- // Copyright 2023 DXOS.org
2
+ // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type Resource } from '@dxos/react-ui';
5
+ import { type Resource } from '../common';
6
6
 
7
- import { SETTINGS_PLUGIN } from './actions';
7
+ import { meta } from './meta';
8
8
 
9
9
  export const translations = [
10
10
  {
11
11
  'en-US': {
12
- [SETTINGS_PLUGIN]: {
12
+ [meta.id]: {
13
13
  'open settings label': 'Open settings',
14
14
  'app settings label': 'Settings',
15
15
  'custom plugins label': 'Plugins',
@@ -0,0 +1,33 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React from 'react';
7
+
8
+ import { withTheme } from '@dxos/react-ui/testing';
9
+
10
+ import { useApp } from './useApp';
11
+
12
+ const DefaultStory = () => {
13
+ const App = useApp({
14
+ placeholder: () => <div className='fixed inset-0 flex items-center justify-center'>Loading...</div>,
15
+ });
16
+
17
+ return <App />;
18
+ };
19
+
20
+ const meta = {
21
+ title: 'sdk/app-framework/App',
22
+ render: DefaultStory,
23
+ decorators: [withTheme],
24
+ parameters: {
25
+ layout: 'fullscreen',
26
+ },
27
+ } satisfies Meta;
28
+
29
+ export default meta;
30
+
31
+ type Story = StoryObj<typeof meta>;
32
+
33
+ export const Default: Story = {};
@@ -0,0 +1,59 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { type PropsWithChildren } from 'react';
6
+
7
+ import { Capabilities } from '../common';
8
+ import { topologicalSort } from '../helpers';
9
+
10
+ import { type UseAppOptions } from './useApp';
11
+ import { useCapabilities } from './useCapabilities';
12
+ import { LoadingState, useLoading } from './useLoading';
13
+
14
+ export type AppProps = Pick<UseAppOptions, 'placeholder' | 'debounce'> & {
15
+ state: { ready: boolean; error: unknown };
16
+ };
17
+
18
+ export const App = ({ placeholder: Placeholder, state, debounce }: AppProps) => {
19
+ const reactContexts = useCapabilities(Capabilities.ReactContext);
20
+ const reactRoots = useCapabilities(Capabilities.ReactRoot);
21
+ const stage = useLoading(state, debounce);
22
+
23
+ if (state.error) {
24
+ // This triggers the error boundary to provide UI feedback for the startup error.
25
+ throw state.error;
26
+ }
27
+
28
+ // TODO(wittjosiah): Consider using Suspense instead.
29
+ if (stage < LoadingState.Done) {
30
+ if (!Placeholder) {
31
+ return null;
32
+ }
33
+
34
+ return <Placeholder stage={stage} />;
35
+ }
36
+
37
+ const ComposedContext = composeContexts(reactContexts);
38
+ return (
39
+ <ComposedContext>
40
+ {reactRoots.map(({ id, root: Component }) => (
41
+ <Component key={id} />
42
+ ))}
43
+ </ComposedContext>
44
+ );
45
+ };
46
+
47
+ const composeContexts = (contexts: Capabilities.ReactContext[]) => {
48
+ if (contexts.length === 0) {
49
+ return ({ children }: PropsWithChildren) => <>{children}</>;
50
+ }
51
+
52
+ return topologicalSort(contexts)
53
+ .map(({ context }) => context)
54
+ .reduce((Acc, Next) => ({ children }) => (
55
+ <Acc>
56
+ <Next>{children}</Next>
57
+ </Acc>
58
+ ));
59
+ };
@@ -0,0 +1,26 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ /**
8
+ * NOTE: Default fallback should not use tailwind or theme.
9
+ */
10
+ export const DefaultFallback = ({ error }: { error: Error }) => {
11
+ return (
12
+ <div
13
+ style={{
14
+ margin: '1rem',
15
+ padding: '1rem',
16
+ overflow: 'hidden',
17
+ border: '4px solid teal',
18
+ borderRadius: '1rem',
19
+ }}
20
+ >
21
+ {/* TODO(wittjosiah): Link to docs for replacing default. */}
22
+ <h1 style={{ margin: '0.5rem 0', fontSize: '1.2rem' }}>ERROR: {error.message}</h1>
23
+ <pre style={{ overflow: 'auto', fontSize: '1rem', whiteSpace: 'pre-wrap', color: '#888888' }}>{error.stack}</pre>
24
+ </div>
25
+ );
26
+ };
@@ -2,7 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import React, { Component, type FC, type JSX, type PropsWithChildren, type ReactNode } from 'react';
5
+ import React, { Component, type FC, type PropsWithChildren, type ReactNode } from 'react';
6
6
 
7
7
  type State = {
8
8
  error: Error | undefined;
@@ -32,7 +32,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, State> {
32
32
  }
33
33
  }
34
34
 
35
- override render(): string | number | boolean | JSX.Element | Iterable<ReactNode> | null | undefined {
35
+ override render(): ReactNode {
36
36
  if (this.state.error) {
37
37
  const Fallback = this.props.fallback ?? DefaultFallback;
38
38
  return <Fallback data={this.props.data} error={this.state.error} />;
@@ -46,9 +46,11 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, State> {
46
46
  }
47
47
  }
48
48
 
49
- const DefaultFallback: NonNullable<ErrorBoundaryProps['fallback']> = ({ data, error }) => (
50
- <div className='flex flex-col gap-2 overflow-hidden border border-red-500 rounded-sm'>
51
- <pre className='whitespace-pre-wrap font-sm text-xs p-2'>ERROR: {error.message}</pre>
52
- <pre className='whitespace-pre-wrap font-sm text-xs p-2 text-subdued'>{JSON.stringify(data, null, 2)}</pre>
53
- </div>
54
- );
49
+ const DefaultFallback: NonNullable<ErrorBoundaryProps['fallback']> = ({ data, error }) => {
50
+ return (
51
+ <div className='flex flex-col gap-2 overflow-hidden border border-red-500 rounded-sm'>
52
+ <h1 className='p-2'>ERROR: {error.message}</h1>
53
+ <pre className='p-2 overflow-y-auto text-sm text-subdued'>{JSON.stringify(data, null, 2)}</pre>
54
+ </div>
55
+ );
56
+ };