@dxos/app-framework 0.8.4-main.5ea62a8 → 0.8.4-main.72ec0f3

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 (192) hide show
  1. package/.storybook/main.mts +11 -0
  2. package/.storybook/preview.mts +8 -0
  3. package/dist/lib/browser/{app-graph-builder-AFFC6VB2.mjs → app-graph-builder-OIEZZC45.mjs} +31 -30
  4. package/dist/lib/browser/app-graph-builder-OIEZZC45.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-ORWHM7CO.mjs → chunk-SCPE4ZO2.mjs} +11 -8
  6. package/dist/lib/browser/chunk-SCPE4ZO2.mjs.map +7 -0
  7. package/dist/lib/browser/{chunk-DND4XMN4.mjs → chunk-VFUKEZIN.mjs} +233 -191
  8. package/dist/lib/browser/chunk-VFUKEZIN.mjs.map +7 -0
  9. package/dist/lib/browser/{chunk-OZY7HV2A.mjs → chunk-WPW5VVAX.mjs} +281 -273
  10. package/dist/lib/browser/chunk-WPW5VVAX.mjs.map +7 -0
  11. package/dist/lib/browser/index.mjs +14 -56
  12. package/dist/lib/browser/index.mjs.map +3 -3
  13. package/dist/lib/browser/{intent-dispatcher-QG7UPGQX.mjs → intent-dispatcher-LZ4AE66E.mjs} +2 -2
  14. package/dist/lib/browser/{intent-resolver-4S4PSTM5.mjs → intent-resolver-QVCKRX6G.mjs} +7 -7
  15. package/dist/lib/browser/intent-resolver-QVCKRX6G.mjs.map +7 -0
  16. package/dist/lib/browser/meta.json +1 -1
  17. package/dist/lib/browser/react/index.mjs +34 -0
  18. package/dist/lib/browser/{store-6E33KLGK.mjs → store-CNPHOYTJ.mjs} +5 -5
  19. package/dist/lib/browser/store-CNPHOYTJ.mjs.map +7 -0
  20. package/dist/lib/browser/testing/index.mjs +14 -16
  21. package/dist/lib/browser/testing/index.mjs.map +3 -3
  22. package/dist/lib/node-esm/{app-graph-builder-S4OAULX5.mjs → app-graph-builder-EBU4NVWD.mjs} +31 -30
  23. package/dist/lib/node-esm/app-graph-builder-EBU4NVWD.mjs.map +7 -0
  24. package/dist/lib/node-esm/{chunk-E2TK7Z4P.mjs → chunk-IJOHO66N.mjs} +233 -191
  25. package/dist/lib/node-esm/chunk-IJOHO66N.mjs.map +7 -0
  26. package/dist/lib/node-esm/{chunk-F63ZRXMK.mjs → chunk-XJZGUJ3H.mjs} +281 -273
  27. package/dist/lib/node-esm/chunk-XJZGUJ3H.mjs.map +7 -0
  28. package/dist/lib/node-esm/{chunk-UMZQERLE.mjs → chunk-ZX63QUGE.mjs} +11 -8
  29. package/dist/lib/node-esm/chunk-ZX63QUGE.mjs.map +7 -0
  30. package/dist/lib/node-esm/index.mjs +14 -56
  31. package/dist/lib/node-esm/index.mjs.map +3 -3
  32. package/dist/lib/node-esm/{intent-dispatcher-NXBGPJOX.mjs → intent-dispatcher-MGOJ3CHD.mjs} +2 -2
  33. package/dist/lib/node-esm/{intent-resolver-2ZKXI5ET.mjs → intent-resolver-URF3HN3G.mjs} +7 -7
  34. package/dist/lib/node-esm/intent-resolver-URF3HN3G.mjs.map +7 -0
  35. package/dist/lib/node-esm/meta.json +1 -1
  36. package/dist/lib/node-esm/react/index.mjs +35 -0
  37. package/dist/lib/node-esm/{store-QQUTQHHT.mjs → store-RK5B4XEL.mjs} +5 -5
  38. package/dist/lib/node-esm/store-RK5B4XEL.mjs.map +7 -0
  39. package/dist/lib/node-esm/testing/index.mjs +14 -16
  40. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  41. package/dist/types/src/common/capabilities.d.ts +41 -37
  42. package/dist/types/src/common/capabilities.d.ts.map +1 -1
  43. package/dist/types/src/common/collaboration.d.ts +1 -1
  44. package/dist/types/src/common/collaboration.d.ts.map +1 -1
  45. package/dist/types/src/common/file.d.ts +1 -1
  46. package/dist/types/src/common/file.d.ts.map +1 -1
  47. package/dist/types/src/common/layout.d.ts +1 -3
  48. package/dist/types/src/common/layout.d.ts.map +1 -1
  49. package/dist/types/src/common/surface.d.ts +19 -16
  50. package/dist/types/src/common/surface.d.ts.map +1 -1
  51. package/dist/types/src/common/translations.d.ts +1 -1
  52. package/dist/types/src/common/translations.d.ts.map +1 -1
  53. package/dist/types/src/core/capabilities.d.ts +15 -15
  54. package/dist/types/src/core/capabilities.d.ts.map +1 -1
  55. package/dist/types/src/core/manager.d.ts +1 -1
  56. package/dist/types/src/core/manager.d.ts.map +1 -1
  57. package/dist/types/src/core/plugin.d.ts +8 -1
  58. package/dist/types/src/core/plugin.d.ts.map +1 -1
  59. package/dist/types/src/index.d.ts +0 -2
  60. package/dist/types/src/index.d.ts.map +1 -1
  61. package/dist/types/src/playground/debug/plugin.d.ts +1 -1
  62. package/dist/types/src/playground/debug/plugin.d.ts.map +1 -1
  63. package/dist/types/src/playground/generator/Main.d.ts.map +1 -1
  64. package/dist/types/src/playground/generator/generator.d.ts +1 -1
  65. package/dist/types/src/playground/generator/generator.d.ts.map +1 -1
  66. package/dist/types/src/playground/generator/plugin.d.ts +1 -1
  67. package/dist/types/src/playground/generator/plugin.d.ts.map +1 -1
  68. package/dist/types/src/playground/layout/plugin.d.ts +1 -1
  69. package/dist/types/src/playground/layout/plugin.d.ts.map +1 -1
  70. package/dist/types/src/playground/logger/plugin.d.ts +1 -1
  71. package/dist/types/src/playground/logger/plugin.d.ts.map +1 -1
  72. package/dist/types/src/playground/logger/schema.d.ts +1 -1
  73. package/dist/types/src/playground/logger/schema.d.ts.map +1 -1
  74. package/dist/types/src/playground/playground.stories.d.ts +0 -1
  75. package/dist/types/src/playground/playground.stories.d.ts.map +1 -1
  76. package/dist/types/src/plugin-intent/IntentPlugin.d.ts +1 -1
  77. package/dist/types/src/plugin-intent/IntentPlugin.d.ts.map +1 -1
  78. package/dist/types/src/plugin-intent/actions.d.ts +5 -7
  79. package/dist/types/src/plugin-intent/actions.d.ts.map +1 -1
  80. package/dist/types/src/plugin-intent/errors.d.ts.map +1 -1
  81. package/dist/types/src/plugin-intent/intent-dispatcher.d.ts +4 -4
  82. package/dist/types/src/plugin-intent/intent-dispatcher.d.ts.map +1 -1
  83. package/dist/types/src/plugin-intent/intent.d.ts +1 -1
  84. package/dist/types/src/plugin-intent/intent.d.ts.map +1 -1
  85. package/dist/types/src/plugin-intent/meta.d.ts +3 -0
  86. package/dist/types/src/plugin-intent/meta.d.ts.map +1 -0
  87. package/dist/types/src/plugin-settings/SettingsPlugin.d.ts +1 -1
  88. package/dist/types/src/plugin-settings/SettingsPlugin.d.ts.map +1 -1
  89. package/dist/types/src/plugin-settings/actions.d.ts +5 -7
  90. package/dist/types/src/plugin-settings/actions.d.ts.map +1 -1
  91. package/dist/types/src/plugin-settings/app-graph-builder.d.ts.map +1 -1
  92. package/dist/types/src/plugin-settings/meta.d.ts +3 -0
  93. package/dist/types/src/plugin-settings/meta.d.ts.map +1 -0
  94. package/dist/types/src/plugin-settings/translations.d.ts +2 -1
  95. package/dist/types/src/plugin-settings/translations.d.ts.map +1 -1
  96. package/dist/types/src/react/App.d.ts +10 -0
  97. package/dist/types/src/react/App.d.ts.map +1 -0
  98. package/dist/types/src/react/App.stories.d.ts +14 -0
  99. package/dist/types/src/react/App.stories.d.ts.map +1 -0
  100. package/dist/types/src/react/DefaultFallback.d.ts +8 -0
  101. package/dist/types/src/react/DefaultFallback.d.ts.map +1 -0
  102. package/dist/types/src/react/ErrorBoundary.d.ts +2 -2
  103. package/dist/types/src/react/ErrorBoundary.d.ts.map +1 -1
  104. package/dist/types/src/react/Surface.d.ts +5 -5
  105. package/dist/types/src/react/Surface.d.ts.map +1 -1
  106. package/dist/types/src/react/Surface.stories.d.ts +3 -7
  107. package/dist/types/src/react/Surface.stories.d.ts.map +1 -1
  108. package/dist/types/src/react/index.d.ts +2 -0
  109. package/dist/types/src/react/index.d.ts.map +1 -1
  110. package/dist/types/src/react/types.d.ts +14 -0
  111. package/dist/types/src/react/types.d.ts.map +1 -0
  112. package/dist/types/src/{App.d.ts → react/useApp.d.ts} +7 -6
  113. package/dist/types/src/react/useApp.d.ts.map +1 -0
  114. package/dist/types/src/react/useLoading.d.ts +19 -0
  115. package/dist/types/src/react/useLoading.d.ts.map +1 -0
  116. package/dist/types/src/testing/withPluginManager.d.ts +7 -8
  117. package/dist/types/src/testing/withPluginManager.d.ts.map +1 -1
  118. package/dist/types/tsconfig.tsbuildinfo +1 -1
  119. package/moon.yml +5 -1
  120. package/package.json +44 -40
  121. package/src/common/capabilities.ts +34 -19
  122. package/src/common/collaboration.ts +3 -3
  123. package/src/common/file.ts +1 -1
  124. package/src/common/layout.ts +3 -4
  125. package/src/common/surface.ts +23 -21
  126. package/src/common/translations.ts +1 -1
  127. package/src/core/capabilities.test.ts +2 -2
  128. package/src/core/capabilities.ts +26 -22
  129. package/src/core/manager.test.ts +19 -19
  130. package/src/core/manager.ts +14 -7
  131. package/src/core/plugin.ts +13 -2
  132. package/src/index.ts +0 -2
  133. package/src/playground/debug/Debug.tsx +1 -1
  134. package/src/playground/debug/plugin.ts +7 -8
  135. package/src/playground/generator/Main.tsx +0 -1
  136. package/src/playground/generator/generator.ts +2 -2
  137. package/src/playground/generator/plugin.ts +12 -13
  138. package/src/playground/layout/plugin.ts +9 -8
  139. package/src/playground/logger/plugin.ts +27 -23
  140. package/src/playground/logger/schema.ts +1 -1
  141. package/src/playground/playground.stories.tsx +6 -7
  142. package/src/plugin-intent/IntentPlugin.ts +12 -13
  143. package/src/plugin-intent/actions.ts +4 -6
  144. package/src/plugin-intent/errors.ts +2 -1
  145. package/src/plugin-intent/intent-dispatcher.test.ts +10 -3
  146. package/src/plugin-intent/intent-dispatcher.ts +13 -6
  147. package/src/plugin-intent/intent.ts +1 -1
  148. package/src/plugin-intent/meta.ts +10 -0
  149. package/src/plugin-settings/SettingsPlugin.ts +25 -27
  150. package/src/plugin-settings/actions.ts +9 -13
  151. package/src/plugin-settings/app-graph-builder.ts +22 -20
  152. package/src/plugin-settings/intent-resolver.ts +2 -2
  153. package/src/plugin-settings/meta.ts +10 -0
  154. package/src/plugin-settings/store.ts +2 -2
  155. package/src/plugin-settings/translations.ts +4 -4
  156. package/src/react/App.stories.tsx +33 -0
  157. package/src/react/App.tsx +59 -0
  158. package/src/react/DefaultFallback.tsx +26 -0
  159. package/src/react/ErrorBoundary.tsx +10 -8
  160. package/src/react/Surface.stories.tsx +70 -49
  161. package/src/react/Surface.tsx +67 -36
  162. package/src/react/index.ts +4 -0
  163. package/src/react/types.ts +37 -0
  164. package/src/{App.tsx → react/useApp.tsx} +35 -150
  165. package/src/react/useCapabilities.ts +2 -2
  166. package/src/react/useLoading.tsx +70 -0
  167. package/src/testing/withPluginManager.stories.tsx +1 -1
  168. package/src/testing/withPluginManager.tsx +24 -23
  169. package/tsconfig.json +11 -9
  170. package/vitest.config.ts +8 -6
  171. package/dist/lib/browser/app-graph-builder-AFFC6VB2.mjs.map +0 -7
  172. package/dist/lib/browser/chunk-DND4XMN4.mjs.map +0 -7
  173. package/dist/lib/browser/chunk-ORWHM7CO.mjs.map +0 -7
  174. package/dist/lib/browser/chunk-OZY7HV2A.mjs.map +0 -7
  175. package/dist/lib/browser/intent-resolver-4S4PSTM5.mjs.map +0 -7
  176. package/dist/lib/browser/store-6E33KLGK.mjs.map +0 -7
  177. package/dist/lib/browser/worker.mjs +0 -85
  178. package/dist/lib/node-esm/app-graph-builder-S4OAULX5.mjs.map +0 -7
  179. package/dist/lib/node-esm/chunk-E2TK7Z4P.mjs.map +0 -7
  180. package/dist/lib/node-esm/chunk-F63ZRXMK.mjs.map +0 -7
  181. package/dist/lib/node-esm/chunk-UMZQERLE.mjs.map +0 -7
  182. package/dist/lib/node-esm/intent-resolver-2ZKXI5ET.mjs.map +0 -7
  183. package/dist/lib/node-esm/store-QQUTQHHT.mjs.map +0 -7
  184. package/dist/lib/node-esm/worker.mjs +0 -86
  185. package/dist/types/src/App.d.ts.map +0 -1
  186. package/dist/types/src/worker.d.ts +0 -4
  187. package/dist/types/src/worker.d.ts.map +0 -1
  188. package/src/worker.ts +0 -11
  189. /package/dist/lib/browser/{intent-dispatcher-QG7UPGQX.mjs.map → intent-dispatcher-LZ4AE66E.mjs.map} +0 -0
  190. /package/dist/lib/browser/{worker.mjs.map → react/index.mjs.map} +0 -0
  191. /package/dist/lib/node-esm/{intent-dispatcher-NXBGPJOX.mjs.map → intent-dispatcher-MGOJ3CHD.mjs.map} +0 -0
  192. /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,6 @@ export const createDispatcher = (
197
200
  .filter((resolver) => !resolver.filter || resolver.filter(intent.data))
198
201
  .toSorted(byPosition);
199
202
  if (candidates.length === 0) {
200
- log.info('no resolvers found', { intent: intent.id });
201
203
  return yield* Effect.fail(new NoResolversError(intent.id));
202
204
  }
203
205
 
@@ -207,6 +209,7 @@ export const createDispatcher = (
207
209
  });
208
210
 
209
211
  const dispatch: IntentDispatcher = (intentChain, depth = 0) => {
212
+ log('dispatch', { intentChain: intentChain.all.map((i) => i.id), depth });
210
213
  return Effect.gen(function* () {
211
214
  if (depth > executionLimit) {
212
215
  return yield* Effect.fail(new CycleDetectedError());
@@ -214,8 +217,10 @@ export const createDispatcher = (
214
217
 
215
218
  const resultsRef = yield* Ref.make<AnyIntentResult[]>([]);
216
219
  for (const intent of intentChain.all) {
220
+ log('processing', { intent });
217
221
  const { data: prev } = (yield* resultsRef.get)[0] ?? {};
218
222
  const result = yield* handleIntent({ ...intent, data: { ...intent.data, ...prev } });
223
+ log('ok', { intent: intent.id, result });
219
224
  yield* Ref.update(resultsRef, (results) => [result, ...results]);
220
225
  if (result.intents) {
221
226
  for (const intent of result.intents) {
@@ -232,6 +237,7 @@ export const createDispatcher = (
232
237
  // error: result.error.message,
233
238
  // }),
234
239
  // );
240
+ log.error('failed', { intent: intent.id, error: result.error });
235
241
  return yield* Effect.fail(result.error);
236
242
  }
237
243
  }
@@ -253,7 +259,7 @@ export const createDispatcher = (
253
259
 
254
260
  if (result.undoable && isUndoable(results)) {
255
261
  // TODO(wittjosiah): Is there a better way to handle showing undo for chains?
256
- yield* pipe(
262
+ yield* Function.pipe(
257
263
  dispatch(createIntent(IntentAction.ShowUndo, { message: result.undoable.message })),
258
264
  Effect.catchSome((err) =>
259
265
  err instanceof NoResolversError ? Option.some(Effect.succeed(undefined)) : Option.none(),
@@ -261,6 +267,7 @@ export const createDispatcher = (
261
267
  );
262
268
  }
263
269
 
270
+ log('done', { intent: intentChain.all.map((i) => i.id), result: result.data });
264
271
  return result.data;
265
272
  });
266
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
+ };