@dxos/plugin-testing 0.8.4-main.c85a9c8dae → 0.8.4-main.dfabb4ec29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/dist/lib/neutral/StorybookPlugin.mjs +10 -0
  2. package/dist/lib/neutral/StorybookPlugin.mjs.map +7 -0
  3. package/dist/lib/neutral/add-toast-Z2RXG4RX.mjs +23 -0
  4. package/dist/lib/neutral/add-toast-Z2RXG4RX.mjs.map +7 -0
  5. package/dist/lib/neutral/capabilities/index.mjs +11 -0
  6. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  7. package/dist/lib/neutral/chunk-7UPIIIZM.mjs +22 -0
  8. package/dist/lib/neutral/chunk-7UPIIIZM.mjs.map +7 -0
  9. package/dist/lib/neutral/chunk-E4LBVPH5.mjs +8 -0
  10. package/dist/lib/neutral/chunk-E4LBVPH5.mjs.map +7 -0
  11. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  12. package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
  13. package/dist/lib/neutral/chunk-NZ2VT6N2.mjs +17 -0
  14. package/dist/lib/neutral/chunk-NZ2VT6N2.mjs.map +7 -0
  15. package/dist/lib/neutral/chunk-R7AV233N.mjs +31 -0
  16. package/dist/lib/neutral/chunk-R7AV233N.mjs.map +7 -0
  17. package/dist/lib/neutral/chunk-ULNF6GTG.mjs +21 -0
  18. package/dist/lib/neutral/chunk-ULNF6GTG.mjs.map +7 -0
  19. package/dist/lib/{browser/chunk-YHPXIILW.mjs → neutral/chunk-ZVN23V74.mjs} +3 -8
  20. package/dist/lib/neutral/chunk-ZVN23V74.mjs.map +7 -0
  21. package/dist/lib/neutral/close-L3FBJQ3J.mjs +12 -0
  22. package/dist/lib/neutral/close-L3FBJQ3J.mjs.map +7 -0
  23. package/dist/lib/{browser → neutral/components}/index.mjs +54 -76
  24. package/dist/lib/neutral/components/index.mjs.map +7 -0
  25. package/dist/lib/neutral/core.mjs +8 -0
  26. package/dist/lib/neutral/core.mjs.map +7 -0
  27. package/dist/lib/neutral/harness.mjs +37 -0
  28. package/dist/lib/neutral/harness.mjs.map +7 -0
  29. package/dist/lib/neutral/index.mjs +20 -0
  30. package/dist/lib/neutral/index.mjs.map +7 -0
  31. package/dist/lib/neutral/meta.json +1 -0
  32. package/dist/lib/neutral/meta.mjs +8 -0
  33. package/dist/lib/neutral/meta.mjs.map +7 -0
  34. package/dist/lib/neutral/open-WBONYQ47.mjs +14 -0
  35. package/dist/lib/neutral/open-WBONYQ47.mjs.map +7 -0
  36. package/dist/lib/neutral/operation-handler-LQIWHQVY.mjs +13 -0
  37. package/dist/lib/neutral/operation-handler-LQIWHQVY.mjs.map +7 -0
  38. package/dist/lib/neutral/operations/index.mjs +8 -0
  39. package/dist/lib/neutral/operations/index.mjs.map +7 -0
  40. package/dist/lib/neutral/plugin.mjs +16 -0
  41. package/dist/lib/neutral/plugin.mjs.map +7 -0
  42. package/dist/lib/neutral/scroll-into-view-BKOOOECC.mjs +12 -0
  43. package/dist/lib/neutral/scroll-into-view-BKOOOECC.mjs.map +7 -0
  44. package/dist/lib/neutral/set-layout-mode-2KHD2AEY.mjs +12 -0
  45. package/dist/lib/neutral/set-layout-mode-2KHD2AEY.mjs.map +7 -0
  46. package/dist/lib/{browser/state-MIKI67FF.mjs → neutral/state-AJ62JEEG.mjs} +6 -6
  47. package/dist/lib/neutral/state-AJ62JEEG.mjs.map +7 -0
  48. package/dist/lib/neutral/switch-workspace-SAT2NGXV.mjs +20 -0
  49. package/dist/lib/neutral/switch-workspace-SAT2NGXV.mjs.map +7 -0
  50. package/dist/lib/neutral/types/index.mjs +8 -0
  51. package/dist/lib/neutral/types/index.mjs.map +7 -0
  52. package/dist/lib/neutral/update-complementary-JP2OL73Z.mjs +26 -0
  53. package/dist/lib/neutral/update-complementary-JP2OL73Z.mjs.map +7 -0
  54. package/dist/lib/neutral/update-dialog-MX6IKKJX.mjs +28 -0
  55. package/dist/lib/neutral/update-dialog-MX6IKKJX.mjs.map +7 -0
  56. package/dist/lib/neutral/update-popover-DOORE3TD.mjs +46 -0
  57. package/dist/lib/neutral/update-popover-DOORE3TD.mjs.map +7 -0
  58. package/dist/lib/neutral/update-sidebar-LWQ3IA6S.mjs +26 -0
  59. package/dist/lib/neutral/update-sidebar-LWQ3IA6S.mjs.map +7 -0
  60. package/dist/types/src/StorybookPlugin.d.ts +3 -2
  61. package/dist/types/src/StorybookPlugin.d.ts.map +1 -1
  62. package/dist/types/src/capabilities/index.d.ts +15 -2
  63. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  64. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  65. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  66. package/dist/types/src/capabilities/{state/state.d.ts → state.d.ts} +3 -3
  67. package/dist/types/src/capabilities/state.d.ts.map +1 -0
  68. package/dist/types/src/components/Layout/Layout.d.ts.map +1 -0
  69. package/dist/types/src/components/Layout/index.d.ts +2 -0
  70. package/dist/types/src/components/Layout/index.d.ts.map +1 -0
  71. package/dist/types/src/core.d.ts +1 -7
  72. package/dist/types/src/core.d.ts.map +1 -1
  73. package/dist/types/src/harness.d.ts +20 -0
  74. package/dist/types/src/harness.d.ts.map +1 -0
  75. package/dist/types/src/index.d.ts +1 -0
  76. package/dist/types/src/index.d.ts.map +1 -1
  77. package/dist/types/src/operations/add-toast.d.ts +5 -0
  78. package/dist/types/src/operations/add-toast.d.ts.map +1 -0
  79. package/dist/types/src/operations/close.d.ts +5 -0
  80. package/dist/types/src/operations/close.d.ts.map +1 -0
  81. package/dist/types/src/operations/index.d.ts +3 -0
  82. package/dist/types/src/operations/index.d.ts.map +1 -0
  83. package/dist/types/src/operations/open.d.ts +5 -0
  84. package/dist/types/src/operations/open.d.ts.map +1 -0
  85. package/dist/types/src/operations/scroll-into-view.d.ts +5 -0
  86. package/dist/types/src/operations/scroll-into-view.d.ts.map +1 -0
  87. package/dist/types/src/operations/set-layout-mode.d.ts +5 -0
  88. package/dist/types/src/operations/set-layout-mode.d.ts.map +1 -0
  89. package/dist/types/src/operations/switch-workspace.d.ts +5 -0
  90. package/dist/types/src/operations/switch-workspace.d.ts.map +1 -0
  91. package/dist/types/src/operations/update-complementary.d.ts +5 -0
  92. package/dist/types/src/operations/update-complementary.d.ts.map +1 -0
  93. package/dist/types/src/operations/update-dialog.d.ts +5 -0
  94. package/dist/types/src/operations/update-dialog.d.ts.map +1 -0
  95. package/dist/types/src/operations/update-popover.d.ts +5 -0
  96. package/dist/types/src/operations/update-popover.d.ts.map +1 -0
  97. package/dist/types/src/operations/update-sidebar.d.ts +5 -0
  98. package/dist/types/src/operations/update-sidebar.d.ts.map +1 -0
  99. package/dist/types/src/operations/update-state.d.ts +5 -0
  100. package/dist/types/src/operations/update-state.d.ts.map +1 -0
  101. package/dist/types/src/plugin.d.ts +5 -0
  102. package/dist/types/src/plugin.d.ts.map +1 -0
  103. package/dist/types/src/types/{capabilities.d.ts → StorybookCapabilities.d.ts} +3 -1
  104. package/dist/types/src/types/StorybookCapabilities.d.ts.map +1 -0
  105. package/dist/types/src/types/index.d.ts +1 -1
  106. package/dist/types/src/types/index.d.ts.map +1 -1
  107. package/dist/types/tsconfig.tsbuildinfo +1 -1
  108. package/package.json +60 -19
  109. package/src/StorybookPlugin.ts +10 -8
  110. package/src/capabilities/index.ts +8 -2
  111. package/src/capabilities/operation-handler.ts +16 -0
  112. package/src/capabilities/{state/state.tsx → state.tsx} +9 -5
  113. package/src/components/Layout/Layout.tsx +236 -0
  114. package/src/components/Layout/index.ts +5 -0
  115. package/src/core.ts +19 -9
  116. package/src/harness.ts +52 -0
  117. package/src/index.ts +6 -0
  118. package/src/meta.ts +1 -1
  119. package/src/operations/add-toast.ts +22 -0
  120. package/src/operations/close.ts +14 -0
  121. package/src/operations/index.ts +18 -0
  122. package/src/operations/open.ts +18 -0
  123. package/src/operations/scroll-into-view.ts +14 -0
  124. package/src/operations/set-layout-mode.ts +14 -0
  125. package/src/operations/switch-workspace.ts +20 -0
  126. package/src/operations/update-complementary.ts +27 -0
  127. package/src/operations/update-dialog.ts +27 -0
  128. package/src/operations/update-popover.ts +37 -0
  129. package/src/operations/update-sidebar.ts +26 -0
  130. package/src/operations/update-state.ts +19 -0
  131. package/src/plugin.ts +12 -0
  132. package/src/types/{capabilities.ts → StorybookCapabilities.ts} +7 -2
  133. package/src/types/index.ts +1 -1
  134. package/dist/lib/browser/chunk-YHPXIILW.mjs.map +0 -7
  135. package/dist/lib/browser/index.mjs.map +0 -7
  136. package/dist/lib/browser/meta.json +0 -1
  137. package/dist/lib/browser/operation-resolver-FLU4ZARD.mjs +0 -112
  138. package/dist/lib/browser/operation-resolver-FLU4ZARD.mjs.map +0 -7
  139. package/dist/lib/browser/state-MIKI67FF.mjs.map +0 -7
  140. package/dist/lib/node-esm/chunk-OWK6XE6C.mjs +0 -23
  141. package/dist/lib/node-esm/chunk-OWK6XE6C.mjs.map +0 -7
  142. package/dist/lib/node-esm/index.mjs +0 -206
  143. package/dist/lib/node-esm/index.mjs.map +0 -7
  144. package/dist/lib/node-esm/meta.json +0 -1
  145. package/dist/lib/node-esm/operation-resolver-NWF6KBHP.mjs +0 -113
  146. package/dist/lib/node-esm/operation-resolver-NWF6KBHP.mjs.map +0 -7
  147. package/dist/lib/node-esm/state-JQVVUKO2.mjs +0 -44
  148. package/dist/lib/node-esm/state-JQVVUKO2.mjs.map +0 -7
  149. package/dist/types/src/capabilities/operation-resolver/index.d.ts +0 -3
  150. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +0 -1
  151. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +0 -5
  152. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +0 -1
  153. package/dist/types/src/capabilities/state/index.d.ts +0 -14
  154. package/dist/types/src/capabilities/state/index.d.ts.map +0 -1
  155. package/dist/types/src/capabilities/state/state.d.ts.map +0 -1
  156. package/dist/types/src/components/Layout.d.ts.map +0 -1
  157. package/dist/types/src/types/capabilities.d.ts.map +0 -1
  158. package/src/capabilities/operation-resolver/index.ts +0 -7
  159. package/src/capabilities/operation-resolver/operation-resolver.ts +0 -99
  160. package/src/capabilities/state/index.ts +0 -7
  161. package/src/components/Layout.tsx +0 -176
  162. /package/dist/types/src/components/{Layout.d.ts → Layout/Layout.d.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-testing",
3
- "version": "0.8.4-main.c85a9c8dae",
3
+ "version": "0.8.4-main.dfabb4ec29",
4
4
  "description": "Plugin testing utils",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -12,11 +12,53 @@
12
12
  "author": "DXOS.org",
13
13
  "sideEffects": true,
14
14
  "type": "module",
15
+ "imports": {
16
+ "#capabilities": {
17
+ "source": "./src/capabilities/index.ts",
18
+ "types": "./dist/types/src/capabilities/index.d.ts",
19
+ "default": "./dist/lib/neutral/capabilities/index.mjs"
20
+ },
21
+ "#components": {
22
+ "source": "./src/components/index.ts",
23
+ "types": "./dist/types/src/components/index.d.ts",
24
+ "default": "./dist/lib/neutral/components/index.mjs"
25
+ },
26
+ "#meta": {
27
+ "source": "./src/meta.ts",
28
+ "types": "./dist/types/src/meta.d.ts",
29
+ "default": "./dist/lib/neutral/meta.mjs"
30
+ },
31
+ "#operations": {
32
+ "source": "./src/operations/index.ts",
33
+ "types": "./dist/types/src/operations/index.d.ts",
34
+ "default": "./dist/lib/neutral/operations/index.mjs"
35
+ },
36
+ "#plugin": {
37
+ "source": "./src/StorybookPlugin.ts",
38
+ "types": "./dist/types/src/StorybookPlugin.d.ts",
39
+ "default": "./dist/lib/neutral/StorybookPlugin.mjs"
40
+ },
41
+ "#types": {
42
+ "source": "./src/types/index.ts",
43
+ "types": "./dist/types/src/types/index.d.ts",
44
+ "default": "./dist/lib/neutral/types/index.mjs"
45
+ }
46
+ },
15
47
  "exports": {
16
48
  ".": {
17
49
  "source": "./src/index.ts",
18
50
  "types": "./dist/types/src/index.d.ts",
19
- "browser": "./dist/lib/browser/index.mjs"
51
+ "default": "./dist/lib/neutral/index.mjs"
52
+ },
53
+ "./harness": {
54
+ "source": "./src/harness.ts",
55
+ "types": "./dist/types/src/harness.d.ts",
56
+ "default": "./dist/lib/neutral/harness.mjs"
57
+ },
58
+ "./plugin": {
59
+ "source": "./src/plugin.ts",
60
+ "types": "./dist/types/src/plugin.d.ts",
61
+ "default": "./dist/lib/neutral/plugin.mjs"
20
62
  }
21
63
  },
22
64
  "types": "dist/types/src/index.d.ts",
@@ -27,33 +69,32 @@
27
69
  "dependencies": {
28
70
  "@effect-atom/atom": "^0.5.1",
29
71
  "@effect-atom/atom-react": "^0.5.0",
30
- "@dxos/app-framework": "0.8.4-main.c85a9c8dae",
31
- "@dxos/operation": "0.8.4-main.c85a9c8dae",
32
- "@dxos/app-toolkit": "0.8.4-main.c85a9c8dae",
33
- "@dxos/plugin-graph": "0.8.4-main.c85a9c8dae",
34
- "@dxos/plugin-settings": "0.8.4-main.c85a9c8dae",
35
- "@dxos/plugin-client": "0.8.4-main.c85a9c8dae",
36
- "@dxos/plugin-theme": "0.8.4-main.c85a9c8dae",
37
- "@dxos/plugin-attention": "0.8.4-main.c85a9c8dae",
38
- "@dxos/react-ui-mosaic": "0.8.4-main.c85a9c8dae",
39
- "@dxos/util": "0.8.4-main.c85a9c8dae"
72
+ "@dxos/app-framework": "0.8.4-main.dfabb4ec29",
73
+ "@dxos/plugin-settings": "0.8.4-main.dfabb4ec29",
74
+ "@dxos/app-toolkit": "0.8.4-main.dfabb4ec29",
75
+ "@dxos/plugin-attention": "0.8.4-main.dfabb4ec29",
76
+ "@dxos/plugin-graph": "0.8.4-main.dfabb4ec29",
77
+ "@dxos/plugin-theme": "0.8.4-main.dfabb4ec29",
78
+ "@dxos/react-ui-mosaic": "0.8.4-main.dfabb4ec29",
79
+ "@dxos/util": "0.8.4-main.dfabb4ec29",
80
+ "@dxos/compute": "0.8.4-main.dfabb4ec29"
40
81
  },
41
82
  "devDependencies": {
42
83
  "@types/react": "~19.2.7",
43
84
  "@types/react-dom": "~19.2.3",
44
- "effect": "3.19.16",
85
+ "effect": "3.20.0",
45
86
  "react": "~19.2.3",
46
87
  "react-dom": "~19.2.3",
47
- "vite": "^7.1.11",
48
- "@dxos/react-ui": "0.8.4-main.c85a9c8dae",
49
- "@dxos/ui-theme": "0.8.4-main.c85a9c8dae"
88
+ "vite": "^8.0.10",
89
+ "@dxos/react-ui": "0.8.4-main.dfabb4ec29",
90
+ "@dxos/ui-theme": "0.8.4-main.dfabb4ec29"
50
91
  },
51
92
  "peerDependencies": {
52
- "effect": "3.19.16",
93
+ "effect": "3.20.0",
53
94
  "react": "~19.2.3",
54
95
  "react-dom": "~19.2.3",
55
- "@dxos/react-ui": "0.8.4-main.c85a9c8dae",
56
- "@dxos/ui-theme": "0.8.4-main.c85a9c8dae"
96
+ "@dxos/react-ui": "0.8.4-main.dfabb4ec29",
97
+ "@dxos/ui-theme": "0.8.4-main.dfabb4ec29"
57
98
  },
58
99
  "publishConfig": {
59
100
  "access": "public"
@@ -7,18 +7,18 @@ import * as Effect from 'effect/Effect';
7
7
  import { ActivationEvents, Capabilities, Capability, Plugin } from '@dxos/app-framework';
8
8
  import { AppActivationEvents, AppPlugin } from '@dxos/app-toolkit';
9
9
 
10
- import { OperationResolver, State } from './capabilities';
11
- import { Layout } from './components';
12
- import { meta } from './meta';
13
- import { type LayoutStateProps } from './types';
10
+ import { OperationHandler, State } from '#capabilities';
11
+ import { Layout } from '#components';
12
+ import { meta } from '#meta';
13
+ import { StorybookCapabilities } from '#types';
14
14
 
15
15
  export type StorybookPluginOptions = {
16
- initialState?: Partial<LayoutStateProps>;
16
+ initialState?: Partial<StorybookCapabilities.LayoutStateProps>;
17
17
  };
18
18
 
19
19
  export const StorybookPlugin = Plugin.define<StorybookPluginOptions>(meta).pipe(
20
- AppPlugin.addOperationResolverModule({
21
- activate: OperationResolver,
20
+ AppPlugin.addOperationHandlerModule({
21
+ activate: OperationHandler,
22
22
  }),
23
23
  AppPlugin.addReactContextModule({
24
24
  activate: () =>
@@ -32,8 +32,10 @@ export const StorybookPlugin = Plugin.define<StorybookPluginOptions>(meta).pipe(
32
32
  Plugin.addModule(({ initialState }) => ({
33
33
  id: Capability.getModuleTag(State),
34
34
  activatesOn: ActivationEvents.Startup,
35
- activatesAfter: [AppActivationEvents.LayoutReady],
35
+ firesAfterActivation: [AppActivationEvents.LayoutReady],
36
36
  activate: () => State({ initialState }),
37
37
  })),
38
38
  Plugin.make,
39
39
  );
40
+
41
+ export default StorybookPlugin;
@@ -2,5 +2,11 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export * from './operation-resolver';
6
- export * from './state';
5
+ import { Capability } from '@dxos/app-framework';
6
+ import { OperationHandlerSet } from '@dxos/compute';
7
+
8
+ export const OperationHandler = Capability.lazy<OperationHandlerSet.OperationHandlerSet>(
9
+ 'OperationHandler',
10
+ () => import('./operation-handler'),
11
+ );
12
+ export const State = Capability.lazy('State', () => import('./state'));
@@ -0,0 +1,16 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capabilities, Capability } from '@dxos/app-framework';
8
+ import type { OperationHandlerSet } from '@dxos/compute';
9
+
10
+ import { TestingOperationHandlerSet } from '#operations';
11
+
12
+ export default Capability.makeModule<OperationHandlerSet.OperationHandlerSet>(
13
+ Effect.fnUntraced(function* () {
14
+ return Capability.contributes(Capabilities.OperationHandler, TestingOperationHandlerSet);
15
+ }),
16
+ );
@@ -8,19 +8,20 @@ import * as Effect from 'effect/Effect';
8
8
  import { Capability } from '@dxos/app-framework';
9
9
  import { AppCapabilities } from '@dxos/app-toolkit';
10
10
 
11
- import { LayoutState, type LayoutStateProps } from '../../types';
11
+ import { StorybookCapabilities } from '#types';
12
12
 
13
- const defaultState: LayoutStateProps = {
13
+ const defaultState: StorybookCapabilities.LayoutStateProps = {
14
14
  sidebarState: 'closed',
15
15
  complementarySidebarState: 'closed',
16
16
  dialogOpen: false,
17
+ toasts: [],
17
18
  workspace: 'default',
18
19
  };
19
20
 
20
21
  export default Capability.makeModule(
21
- Effect.fnUntraced(function* (props?: { initialState?: Partial<LayoutStateProps> }) {
22
+ Effect.fnUntraced(function* (props?: { initialState?: Partial<StorybookCapabilities.LayoutStateProps> }) {
22
23
  const { initialState } = props ?? {};
23
- const stateAtom = Atom.make<LayoutStateProps>({ ...defaultState, ...initialState });
24
+ const stateAtom = Atom.make<StorybookCapabilities.LayoutStateProps>({ ...defaultState, ...initialState });
24
25
 
25
26
  const layoutAtom = Atom.make((get): AppCapabilities.Layout => {
26
27
  const state = get(stateAtom);
@@ -36,6 +37,9 @@ export default Capability.makeModule(
36
37
  };
37
38
  });
38
39
 
39
- return [Capability.contributes(LayoutState, stateAtom), Capability.contributes(AppCapabilities.Layout, layoutAtom)];
40
+ return [
41
+ Capability.contributes(StorybookCapabilities.LayoutState, stateAtom),
42
+ Capability.contributes(AppCapabilities.Layout, layoutAtom),
43
+ ];
40
44
  }),
41
45
  );
@@ -0,0 +1,236 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { RegistryContext, useAtomValue } from '@effect-atom/atom-react';
6
+ import React, { type PropsWithChildren, useCallback, useContext, useEffect, useRef, useState } from 'react';
7
+
8
+ import { Surface, useCapability } from '@dxos/app-framework/ui';
9
+ import { type LayoutOperation } from '@dxos/app-toolkit';
10
+ import { AppSurface } from '@dxos/app-toolkit/ui';
11
+ import {
12
+ AlertDialog,
13
+ Button,
14
+ Dialog,
15
+ Icon,
16
+ Main,
17
+ Popover,
18
+ type PopoverContentInteractOutsideEvent,
19
+ Toast,
20
+ toLocalizedString,
21
+ useTranslation,
22
+ } from '@dxos/react-ui';
23
+ import { Card } from '@dxos/react-ui';
24
+ import { Mosaic } from '@dxos/react-ui-mosaic';
25
+ import { descriptionMessage, mx } from '@dxos/ui-theme';
26
+
27
+ import { meta } from '#meta';
28
+ import { StorybookCapabilities } from '#types';
29
+
30
+ const debounce_delay = 100;
31
+
32
+ const StoryToast = ({ toast, onDismiss }: { toast: LayoutOperation.Toast; onDismiss: (id: string) => void }) => {
33
+ const { t } = useTranslation(meta.id);
34
+ return (
35
+ <Toast.Root
36
+ data-testid={toast.id}
37
+ defaultOpen
38
+ duration={toast.duration}
39
+ onOpenChange={(open) => {
40
+ if (!open) {
41
+ onDismiss(toast.id);
42
+ }
43
+ }}
44
+ >
45
+ <Toast.Body>
46
+ <Toast.Title classNames='items-center'>
47
+ {toast.icon && <Icon icon={toast.icon} classNames='inline mr-1' />}
48
+ {toast.title && <span>{toLocalizedString(toast.title, t)}</span>}
49
+ </Toast.Title>
50
+ {toast.description && <Toast.Description>{toLocalizedString(toast.description, t)}</Toast.Description>}
51
+ </Toast.Body>
52
+ <Toast.Actions>
53
+ {toast.onAction && toast.actionAlt && toast.actionLabel && (
54
+ <Toast.Action altText={toLocalizedString(toast.actionAlt, t)} asChild>
55
+ <Button variant='primary' onClick={() => toast.onAction?.()}>
56
+ {toLocalizedString(toast.actionLabel, t)}
57
+ </Button>
58
+ </Toast.Action>
59
+ )}
60
+ {toast.closeLabel && (
61
+ <Toast.Close asChild>
62
+ <Button>{toLocalizedString(toast.closeLabel, t)}</Button>
63
+ </Toast.Close>
64
+ )}
65
+ </Toast.Actions>
66
+ </Toast.Root>
67
+ );
68
+ };
69
+
70
+ export const Layout = ({ children }: PropsWithChildren<{}>) => {
71
+ const { t } = useTranslation(meta.id);
72
+ const trigger = useRef<HTMLButtonElement | null>(null);
73
+ const registry = useContext(RegistryContext);
74
+ const stateAtom = useCapability(StorybookCapabilities.LayoutState);
75
+ const layout = useAtomValue(stateAtom);
76
+ const [iter, setIter] = useState(0);
77
+ const [open, setOpen] = useState(false);
78
+ const debounceRef = useRef<NodeJS.Timeout | null>(null);
79
+
80
+ const updateState = useCallback(
81
+ (updates: Partial<StorybookCapabilities.LayoutStateProps>) => {
82
+ const current = registry.get(stateAtom);
83
+ registry.set(stateAtom, { ...current, ...updates });
84
+ },
85
+ [registry, stateAtom],
86
+ );
87
+
88
+ useEffect(() => {
89
+ setOpen(false);
90
+ if (debounceRef.current) {
91
+ clearTimeout(debounceRef.current);
92
+ debounceRef.current = null;
93
+ }
94
+ trigger.current = layout.popoverAnchor ?? null;
95
+ setIter((iter) => iter + 1);
96
+ if (layout.popoverOpen) {
97
+ debounceRef.current = setTimeout(() => setOpen(true), debounce_delay);
98
+ }
99
+ }, [layout.popoverAnchor, layout.popoverContent, layout.popoverOpen]);
100
+
101
+ const handleClose = useCallback(() => {
102
+ setOpen(false);
103
+ updateState({
104
+ popoverOpen: false,
105
+ popoverAnchor: undefined,
106
+ popoverAnchorId: undefined,
107
+ popoverSide: undefined,
108
+ });
109
+ }, [updateState]);
110
+
111
+ const handleInteractOutside = useCallback(
112
+ (event: KeyboardEvent | PopoverContentInteractOutsideEvent) => {
113
+ if (
114
+ // TODO(thure): CodeMirror should not focus itself when it updates.
115
+ event.type === 'dismissableLayer.focusOutside' &&
116
+ (event.currentTarget as HTMLElement | undefined)?.classList.contains('cm-content')
117
+ ) {
118
+ event.preventDefault();
119
+ } else {
120
+ handleClose();
121
+ }
122
+ },
123
+ [handleClose],
124
+ );
125
+
126
+ const handleDismissToast = useCallback(
127
+ (id: string) => {
128
+ updateState({ toasts: layout.toasts.filter((toast) => toast.id !== id) });
129
+ },
130
+ [updateState, layout.toasts],
131
+ );
132
+
133
+ const DialogRoot = layout.dialogType === 'alert' ? AlertDialog.Root : Dialog.Root;
134
+ const DialogOverlay = layout.dialogType === 'alert' ? AlertDialog.Overlay : Dialog.Overlay;
135
+
136
+ return (
137
+ <Toast.Provider>
138
+ <div className='fixed inset-0 flex overflow-hidden'>
139
+ <Mosaic.Root>
140
+ <Popover.Root open={open}>
141
+ <Main.Root
142
+ navigationSidebarState={layout.sidebarState}
143
+ complementarySidebarState={layout.complementarySidebarState}
144
+ onNavigationSidebarStateChange={(next) => updateState({ sidebarState: next })}
145
+ onComplementarySidebarStateChange={(next) => updateState({ complementarySidebarState: next })}
146
+ >
147
+ {children}
148
+ </Main.Root>
149
+
150
+ <DialogRoot
151
+ modal={layout.dialogBlockAlign !== 'end'}
152
+ open={layout.dialogOpen}
153
+ onOpenChange={(nextOpen) => updateState({ dialogOpen: nextOpen })}
154
+ >
155
+ {layout.dialogBlockAlign === 'end' ? (
156
+ <Surface.Surface
157
+ type={AppSurface.Dialog}
158
+ data={layout.dialogContent}
159
+ limit={1}
160
+ fallback={ErrorFallback}
161
+ placeholder={<div />}
162
+ />
163
+ ) : (
164
+ <DialogOverlay
165
+ blockAlign={layout.dialogBlockAlign}
166
+ classNames={layout.dialogOverlayClasses}
167
+ style={layout.dialogOverlayStyle}
168
+ >
169
+ <Surface.Surface
170
+ type={AppSurface.Dialog}
171
+ data={layout.dialogContent}
172
+ limit={1}
173
+ fallback={ErrorFallback}
174
+ />
175
+ </DialogOverlay>
176
+ )}
177
+ </DialogRoot>
178
+
179
+ <Popover.VirtualTrigger key={iter} virtualRef={trigger} />
180
+ <Popover.Portal>
181
+ <Popover.Content
182
+ side={layout.popoverSide}
183
+ onInteractOutside={handleInteractOutside}
184
+ onEscapeKeyDown={handleInteractOutside}
185
+ sticky='always'
186
+ hideWhenDetached
187
+ >
188
+ <Popover.Viewport>
189
+ {layout.popoverKind === 'card' && (
190
+ <Card.Root>
191
+ <Card.Toolbar>
192
+ {/* TODO(wittjosiah): Cleaner way to handle no drag handle in toolbar? */}
193
+ <span />
194
+ {layout.popoverTitle ? (
195
+ <Card.Title>{toLocalizedString(layout.popoverTitle, t)}</Card.Title>
196
+ ) : (
197
+ <span />
198
+ )}
199
+ <Card.CloseIconButton onClick={handleClose} />
200
+ </Card.Toolbar>
201
+ <Surface.Surface type={AppSurface.Card} data={layout.popoverContent} limit={1} />
202
+ </Card.Root>
203
+ )}
204
+ {layout.popoverKind === 'base' && (
205
+ <Surface.Surface type={AppSurface.Popover} data={layout.popoverContent} limit={1} />
206
+ )}
207
+ </Popover.Viewport>
208
+ <Popover.Arrow />
209
+ </Popover.Content>
210
+ </Popover.Portal>
211
+ </Popover.Root>
212
+ </Mosaic.Root>
213
+ {layout.toasts.map((toast) => (
214
+ <StoryToast key={toast.id} toast={toast} onDismiss={handleDismissToast} />
215
+ ))}
216
+ <Toast.Viewport />
217
+ </div>
218
+ </Toast.Provider>
219
+ );
220
+ };
221
+
222
+ export const ErrorFallback = ({ error }: { error?: Error }) => {
223
+ const { t } = useTranslation(meta.id);
224
+ const errorString = error?.toString() ?? '';
225
+ return (
226
+ <div
227
+ role='alert'
228
+ data-testid='error-boundary-fallback'
229
+ className={mx('overflow-auto p-8 dx-attention-surface grid place-items-center')}
230
+ >
231
+ <p className={mx(descriptionMessage, 'break-words rounded-md p-8', errorString.length < 256 && 'text-lg')}>
232
+ {error ? errorString : t('error-fallback.message')}
233
+ </p>
234
+ </div>
235
+ );
236
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './Layout';
package/src/core.ts CHANGED
@@ -3,17 +3,27 @@
3
3
  //
4
4
 
5
5
  import { OperationPlugin, type Plugin, RuntimePlugin } from '@dxos/app-framework';
6
- import { AttentionPlugin } from '@dxos/plugin-attention';
7
- import { ClientPlugin } from '@dxos/plugin-client';
8
- import { GraphPlugin } from '@dxos/plugin-graph';
9
- import { SettingsPlugin } from '@dxos/plugin-settings';
10
- import { ThemePlugin } from '@dxos/plugin-theme';
6
+ // `/plugin` entrypoints `@dxos/plugin-attention/plugin`, `/plugin-client/plugin`,
7
+ // `/plugin-graph/plugin`, `/plugin-preview/plugin`, `/plugin-settings/plugin`,
8
+ // `/plugin-space/plugin`, `/plugin-theme/plugin` re-export the plugin eagerly
9
+ // (without `Plugin.lazy`). The default `.` export wraps each plugin in a lazy
10
+ // stub (`() => import('./XPlugin')`), which webkit cannot reliably resolve
11
+ // under vite-dev: the dynamic-import promise can settle with a
12
+ // partially-evaluated namespace, throwing `ReferenceError: Cannot access
13
+ // 'default' before initialization` from the loader's `mod.default` access.
14
+ // Storybook runs inside `storybook dev` (vite-dev) and is currently the only
15
+ // host that hits this. Production hosts (composer-app via `vite preview`)
16
+ // keep using the lazy `.` exports and their associated code splitting.
17
+ //
18
+ // Use these `/plugin` entrypoints from any storybook `withPluginManager`
19
+ // setup. The single-line `export * from './XPlugin'` re-exports avoid the
20
+ // dynamic-import path entirely — see e.g. `plugin-attention/src/plugin.ts`.
21
+ import { AttentionPlugin } from '@dxos/plugin-attention/testing';
22
+ import { GraphPlugin } from '@dxos/plugin-graph/testing';
23
+ import { SettingsPlugin } from '@dxos/plugin-settings/testing';
24
+ import { ThemePlugin } from '@dxos/plugin-theme/plugin';
11
25
  import { defaultTx } from '@dxos/ui-theme';
12
26
 
13
- // TODO(burdon): Remove this.
14
- // Re-export common framework plugins.
15
- export { AttentionPlugin, ClientPlugin, GraphPlugin, OperationPlugin, RuntimePlugin, SettingsPlugin, ThemePlugin };
16
-
17
27
  /**
18
28
  * Core plugins for testing/storybook environments.
19
29
  * NOTE: Does not include SpacePlugin to avoid circular dependencies.
package/src/harness.ts ADDED
@@ -0,0 +1,52 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { OperationPlugin, type Plugin, RuntimePlugin } from '@dxos/app-framework';
6
+ import { createTestApp, type TestAppOptions, type TestHarness } from '@dxos/app-framework/testing';
7
+ import { AttentionPlugin } from '@dxos/plugin-attention/plugin';
8
+ import { GraphPlugin } from '@dxos/plugin-graph/plugin';
9
+ import { SettingsPlugin } from '@dxos/plugin-settings/plugin';
10
+
11
+ export type ComposerTestAppOptions = Omit<TestAppOptions, 'plugins'> & {
12
+ /** Plugins to register in addition to the Composer core plugins. */
13
+ plugins?: Plugin.Plugin[];
14
+ /**
15
+ * Whether to include `ThemePlugin` in the core plugin set.
16
+ * Defaults to `false` — `ThemePlugin` requires a browser DOM and breaks Node-only tests.
17
+ * Set to `true` (with jsdom/happy-dom) when rendering React surfaces.
18
+ */
19
+ theme?: boolean;
20
+ };
21
+
22
+ /**
23
+ * Headless core plugins for the test harness — the subset of `corePlugins()`
24
+ * that can be activated without a browser DOM.
25
+ */
26
+ const headlessCorePlugins = (): Plugin.Plugin[] => [
27
+ AttentionPlugin(),
28
+ GraphPlugin(),
29
+ OperationPlugin(),
30
+ RuntimePlugin(),
31
+ SettingsPlugin(),
32
+ ];
33
+
34
+ /**
35
+ * Creates a TestHarness pre-loaded with the Composer core plugins
36
+ * (Attention, Graph, Operation, Runtime, Settings, optionally Theme).
37
+ *
38
+ * For a ClientPlugin-backed harness, pass `ClientPlugin({ ... })` via `plugins`.
39
+ */
40
+ export const createComposerTestApp = async (opts: ComposerTestAppOptions = {}): Promise<TestHarness> => {
41
+ const { plugins = [], theme = false, ...rest } = opts;
42
+ const core = headlessCorePlugins();
43
+ if (theme) {
44
+ const { ThemePlugin } = await import('@dxos/plugin-theme/plugin');
45
+ const { defaultTx } = await import('@dxos/ui-theme');
46
+ core.push(ThemePlugin({ tx: defaultTx }));
47
+ }
48
+ return createTestApp({
49
+ ...rest,
50
+ plugins: [...core, ...plugins],
51
+ });
52
+ };
package/src/index.ts CHANGED
@@ -4,5 +4,11 @@
4
4
 
5
5
  export * from './core';
6
6
  export * from './meta';
7
+ export * from './types';
7
8
 
9
+ // `@dxos/plugin-testing` is a testing-only package, so `StorybookPlugin` is
10
+ // exported eagerly from the `.` entrypoint (no `Plugin.lazy` indirection).
11
+ // Storybook on webkit + vite-dev cannot reliably resolve the lazy form's
12
+ // dynamic `import()` — see `./core.ts` for the underlying chunk-init issue
13
+ // — and there is no production code-splitting benefit to recover here.
8
14
  export * from './StorybookPlugin';
package/src/meta.ts CHANGED
@@ -6,7 +6,7 @@ import { type Plugin } from '@dxos/app-framework';
6
6
  import { trim } from '@dxos/util';
7
7
 
8
8
  export const meta: Plugin.Meta = {
9
- id: 'dxos.org/plugin/storybook-layout',
9
+ id: 'org.dxos.plugin.storybook-layout',
10
10
  name: 'Storybook',
11
11
  description: trim`
12
12
  Development layout optimized for Storybook component testing and documentation.
@@ -0,0 +1,22 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/compute';
9
+
10
+ import { updateState } from './update-state';
11
+
12
+ const handler: Operation.WithHandler<typeof LayoutOperation.AddToast> = LayoutOperation.AddToast.pipe(
13
+ Operation.withHandler(
14
+ Effect.fnUntraced(function* (input) {
15
+ yield* updateState((state) => ({
16
+ toasts: [...state.toasts, input as LayoutOperation.Toast],
17
+ }));
18
+ }),
19
+ ),
20
+ );
21
+
22
+ export default handler;
@@ -0,0 +1,14 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/compute';
9
+
10
+ const handler: Operation.WithHandler<typeof LayoutOperation.Close> = LayoutOperation.Close.pipe(
11
+ Operation.withHandler(() => Effect.void),
12
+ );
13
+
14
+ export default handler;
@@ -0,0 +1,18 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { OperationHandlerSet } from '@dxos/compute';
6
+
7
+ export const TestingOperationHandlerSet = OperationHandlerSet.lazy(
8
+ () => import('./add-toast'),
9
+ () => import('./close'),
10
+ () => import('./open'),
11
+ () => import('./scroll-into-view'),
12
+ () => import('./set-layout-mode'),
13
+ () => import('./switch-workspace'),
14
+ () => import('./update-complementary'),
15
+ () => import('./update-dialog'),
16
+ () => import('./update-popover'),
17
+ () => import('./update-sidebar'),
18
+ );
@@ -0,0 +1,18 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/compute';
9
+
10
+ const handler: Operation.WithHandler<typeof LayoutOperation.Open> = LayoutOperation.Open.pipe(
11
+ Operation.withHandler(
12
+ Effect.fnUntraced(function* (input) {
13
+ return input.subject;
14
+ }),
15
+ ),
16
+ );
17
+
18
+ export default handler;
@@ -0,0 +1,14 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/compute';
9
+
10
+ const handler: Operation.WithHandler<typeof LayoutOperation.ScrollIntoView> = LayoutOperation.ScrollIntoView.pipe(
11
+ Operation.withHandler(() => Effect.void),
12
+ );
13
+
14
+ export default handler;