@dxos/plugin-testing 0.8.4-main.d05673bc65 → 0.8.4-main.d9fc60f731

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 (159) hide show
  1. package/LICENSE +102 -5
  2. package/dist/lib/neutral/StorybookPlugin.mjs +10 -0
  3. package/dist/lib/neutral/StorybookPlugin.mjs.map +7 -0
  4. package/dist/lib/neutral/add-toast-Z2RXG4RX.mjs +23 -0
  5. package/dist/lib/neutral/add-toast-Z2RXG4RX.mjs.map +7 -0
  6. package/dist/lib/neutral/capabilities/index.mjs +11 -0
  7. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  8. package/dist/lib/neutral/chunk-C7HWAFWJ.mjs +21 -0
  9. package/dist/lib/neutral/chunk-C7HWAFWJ.mjs.map +7 -0
  10. package/dist/lib/{browser/chunk-NWOKPVNQ.mjs → neutral/chunk-HI5MWUZZ.mjs} +8 -10
  11. package/dist/lib/neutral/chunk-HI5MWUZZ.mjs.map +7 -0
  12. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  13. package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
  14. package/dist/lib/neutral/chunk-NZ2VT6N2.mjs +17 -0
  15. package/dist/lib/neutral/chunk-NZ2VT6N2.mjs.map +7 -0
  16. package/dist/lib/neutral/chunk-R7AV233N.mjs +31 -0
  17. package/dist/lib/neutral/chunk-R7AV233N.mjs.map +7 -0
  18. package/dist/lib/neutral/chunk-S4IEYCPX.mjs +8 -0
  19. package/dist/lib/neutral/chunk-S4IEYCPX.mjs.map +7 -0
  20. package/dist/lib/neutral/chunk-ULNF6GTG.mjs +21 -0
  21. package/dist/lib/neutral/chunk-ULNF6GTG.mjs.map +7 -0
  22. package/dist/lib/neutral/close-L3FBJQ3J.mjs +12 -0
  23. package/dist/lib/neutral/close-L3FBJQ3J.mjs.map +7 -0
  24. package/dist/lib/{browser → neutral/components}/index.mjs +52 -76
  25. package/dist/lib/neutral/components/index.mjs.map +7 -0
  26. package/dist/lib/neutral/core.mjs +8 -0
  27. package/dist/lib/neutral/core.mjs.map +7 -0
  28. package/dist/lib/neutral/harness.mjs +36 -0
  29. package/dist/lib/neutral/harness.mjs.map +7 -0
  30. package/dist/lib/neutral/index.mjs +20 -0
  31. package/dist/lib/neutral/index.mjs.map +7 -0
  32. package/dist/lib/neutral/meta.json +1 -0
  33. package/dist/lib/neutral/meta.mjs +8 -0
  34. package/dist/lib/neutral/meta.mjs.map +7 -0
  35. package/dist/lib/neutral/open-O5FFJF2B.mjs +17 -0
  36. package/dist/lib/neutral/open-O5FFJF2B.mjs.map +7 -0
  37. package/dist/lib/neutral/operation-handler-LQIWHQVY.mjs +13 -0
  38. package/dist/lib/neutral/operation-handler-LQIWHQVY.mjs.map +7 -0
  39. package/dist/lib/neutral/operations/index.mjs +8 -0
  40. package/dist/lib/neutral/operations/index.mjs.map +7 -0
  41. package/dist/lib/neutral/plugin.mjs +16 -0
  42. package/dist/lib/neutral/plugin.mjs.map +7 -0
  43. package/dist/lib/neutral/scroll-into-view-BKOOOECC.mjs +12 -0
  44. package/dist/lib/neutral/scroll-into-view-BKOOOECC.mjs.map +7 -0
  45. package/dist/lib/neutral/set-layout-mode-2KHD2AEY.mjs +12 -0
  46. package/dist/lib/neutral/set-layout-mode-2KHD2AEY.mjs.map +7 -0
  47. package/dist/lib/{browser/state-TCYYH5JN.mjs → neutral/state-AJ62JEEG.mjs} +6 -6
  48. package/dist/lib/neutral/state-AJ62JEEG.mjs.map +7 -0
  49. package/dist/lib/neutral/switch-workspace-SAT2NGXV.mjs +20 -0
  50. package/dist/lib/neutral/switch-workspace-SAT2NGXV.mjs.map +7 -0
  51. package/dist/lib/neutral/types/index.mjs +8 -0
  52. package/dist/lib/neutral/types/index.mjs.map +7 -0
  53. package/dist/lib/neutral/update-complementary-JP2OL73Z.mjs +26 -0
  54. package/dist/lib/neutral/update-complementary-JP2OL73Z.mjs.map +7 -0
  55. package/dist/lib/neutral/update-dialog-MX6IKKJX.mjs +28 -0
  56. package/dist/lib/neutral/update-dialog-MX6IKKJX.mjs.map +7 -0
  57. package/dist/lib/neutral/update-popover-DOORE3TD.mjs +46 -0
  58. package/dist/lib/neutral/update-popover-DOORE3TD.mjs.map +7 -0
  59. package/dist/lib/neutral/update-sidebar-LWQ3IA6S.mjs +26 -0
  60. package/dist/lib/neutral/update-sidebar-LWQ3IA6S.mjs.map +7 -0
  61. package/dist/types/src/StorybookPlugin.d.ts +3 -2
  62. package/dist/types/src/StorybookPlugin.d.ts.map +1 -1
  63. package/dist/types/src/capabilities/index.d.ts +15 -2
  64. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  65. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  66. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  67. package/dist/types/src/capabilities/{state/state.d.ts → state.d.ts} +3 -3
  68. package/dist/types/src/capabilities/state.d.ts.map +1 -0
  69. package/dist/types/src/components/Layout/Layout.d.ts.map +1 -1
  70. package/dist/types/src/core.d.ts +1 -7
  71. package/dist/types/src/core.d.ts.map +1 -1
  72. package/dist/types/src/harness.d.ts +20 -0
  73. package/dist/types/src/harness.d.ts.map +1 -0
  74. package/dist/types/src/index.d.ts +1 -0
  75. package/dist/types/src/index.d.ts.map +1 -1
  76. package/dist/types/src/meta.d.ts +1 -1
  77. package/dist/types/src/meta.d.ts.map +1 -1
  78. package/dist/types/src/operations/add-toast.d.ts +5 -0
  79. package/dist/types/src/operations/add-toast.d.ts.map +1 -0
  80. package/dist/types/src/operations/close.d.ts +5 -0
  81. package/dist/types/src/operations/close.d.ts.map +1 -0
  82. package/dist/types/src/operations/index.d.ts +3 -0
  83. package/dist/types/src/operations/index.d.ts.map +1 -0
  84. package/dist/types/src/operations/open.d.ts +5 -0
  85. package/dist/types/src/operations/open.d.ts.map +1 -0
  86. package/dist/types/src/operations/scroll-into-view.d.ts +5 -0
  87. package/dist/types/src/operations/scroll-into-view.d.ts.map +1 -0
  88. package/dist/types/src/operations/set-layout-mode.d.ts +5 -0
  89. package/dist/types/src/operations/set-layout-mode.d.ts.map +1 -0
  90. package/dist/types/src/operations/switch-workspace.d.ts +5 -0
  91. package/dist/types/src/operations/switch-workspace.d.ts.map +1 -0
  92. package/dist/types/src/operations/update-complementary.d.ts +5 -0
  93. package/dist/types/src/operations/update-complementary.d.ts.map +1 -0
  94. package/dist/types/src/operations/update-dialog.d.ts +5 -0
  95. package/dist/types/src/operations/update-dialog.d.ts.map +1 -0
  96. package/dist/types/src/operations/update-popover.d.ts +5 -0
  97. package/dist/types/src/operations/update-popover.d.ts.map +1 -0
  98. package/dist/types/src/operations/update-sidebar.d.ts +5 -0
  99. package/dist/types/src/operations/update-sidebar.d.ts.map +1 -0
  100. package/dist/types/src/operations/update-state.d.ts +5 -0
  101. package/dist/types/src/operations/update-state.d.ts.map +1 -0
  102. package/dist/types/src/plugin.d.ts +5 -0
  103. package/dist/types/src/plugin.d.ts.map +1 -0
  104. package/dist/types/src/types/{capabilities.d.ts → StorybookCapabilities.d.ts} +3 -1
  105. package/dist/types/src/types/StorybookCapabilities.d.ts.map +1 -0
  106. package/dist/types/src/types/index.d.ts +1 -1
  107. package/dist/types/src/types/index.d.ts.map +1 -1
  108. package/dist/types/tsconfig.tsbuildinfo +1 -1
  109. package/package.json +63 -21
  110. package/src/StorybookPlugin.ts +10 -8
  111. package/src/capabilities/index.ts +8 -2
  112. package/src/capabilities/operation-handler.ts +16 -0
  113. package/src/capabilities/{state/state.tsx → state.tsx} +9 -5
  114. package/src/components/Layout/Layout.tsx +128 -76
  115. package/src/core.ts +20 -13
  116. package/src/harness.ts +51 -0
  117. package/src/index.ts +6 -0
  118. package/src/meta.ts +6 -4
  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 +20 -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} +6 -1
  133. package/src/types/index.ts +1 -1
  134. package/dist/lib/browser/chunk-NWOKPVNQ.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-SR4GZ7Q5.mjs +0 -112
  138. package/dist/lib/browser/operation-resolver-SR4GZ7Q5.mjs.map +0 -7
  139. package/dist/lib/browser/state-TCYYH5JN.mjs.map +0 -7
  140. package/dist/lib/node-esm/chunk-NWN7D2LS.mjs +0 -23
  141. package/dist/lib/node-esm/chunk-NWN7D2LS.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-A6XLCTWX.mjs +0 -113
  146. package/dist/lib/node-esm/operation-resolver-A6XLCTWX.mjs.map +0 -7
  147. package/dist/lib/node-esm/state-ZJEK6SP7.mjs +0 -44
  148. package/dist/lib/node-esm/state-ZJEK6SP7.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/types/capabilities.d.ts.map +0 -1
  157. package/src/capabilities/operation-resolver/index.ts +0 -7
  158. package/src/capabilities/operation-resolver/operation-resolver.ts +0 -99
  159. package/src/capabilities/state/index.ts +0 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-testing",
3
- "version": "0.8.4-main.d05673bc65",
3
+ "version": "0.8.4-main.d9fc60f731",
4
4
  "description": "Plugin testing utils",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -8,15 +8,57 @@
8
8
  "type": "git",
9
9
  "url": "https://github.com/dxos/dxos"
10
10
  },
11
- "license": "MIT",
11
+ "license": "FSL-1.1-Apache-2.0",
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",
@@ -25,35 +67,35 @@
25
67
  "src"
26
68
  ],
27
69
  "dependencies": {
28
- "@effect-atom/atom": "^0.5.1",
29
70
  "@effect-atom/atom-react": "^0.5.0",
30
- "@dxos/app-framework": "0.8.4-main.d05673bc65",
31
- "@dxos/app-toolkit": "0.8.4-main.d05673bc65",
32
- "@dxos/operation": "0.8.4-main.d05673bc65",
33
- "@dxos/plugin-attention": "0.8.4-main.d05673bc65",
34
- "@dxos/plugin-client": "0.8.4-main.d05673bc65",
35
- "@dxos/plugin-graph": "0.8.4-main.d05673bc65",
36
- "@dxos/plugin-settings": "0.8.4-main.d05673bc65",
37
- "@dxos/react-ui-mosaic": "0.8.4-main.d05673bc65",
38
- "@dxos/plugin-theme": "0.8.4-main.d05673bc65",
39
- "@dxos/util": "0.8.4-main.d05673bc65"
71
+ "@dxos/app-framework": "0.8.4-main.d9fc60f731",
72
+ "@dxos/app-toolkit": "0.8.4-main.d9fc60f731",
73
+ "@dxos/compute": "0.8.4-main.d9fc60f731",
74
+ "@dxos/keys": "0.8.4-main.d9fc60f731",
75
+ "@dxos/log": "0.8.4-main.d9fc60f731",
76
+ "@dxos/plugin-attention": "0.8.4-main.d9fc60f731",
77
+ "@dxos/plugin-settings": "0.8.4-main.d9fc60f731",
78
+ "@dxos/plugin-graph": "0.8.4-main.d9fc60f731",
79
+ "@dxos/plugin-theme": "0.8.4-main.d9fc60f731",
80
+ "@dxos/react-ui-mosaic": "0.8.4-main.d9fc60f731",
81
+ "@dxos/util": "0.8.4-main.d9fc60f731"
40
82
  },
41
83
  "devDependencies": {
42
84
  "@types/react": "~19.2.7",
43
85
  "@types/react-dom": "~19.2.3",
44
- "effect": "3.19.16",
86
+ "effect": "3.21.2",
45
87
  "react": "~19.2.3",
46
88
  "react-dom": "~19.2.3",
47
- "vite": "^7.1.11",
48
- "@dxos/react-ui": "0.8.4-main.d05673bc65",
49
- "@dxos/ui-theme": "0.8.4-main.d05673bc65"
89
+ "vite": "^8.0.14",
90
+ "@dxos/react-ui": "0.8.4-main.d9fc60f731",
91
+ "@dxos/ui-theme": "0.8.4-main.d9fc60f731"
50
92
  },
51
93
  "peerDependencies": {
52
- "effect": "3.19.16",
94
+ "effect": "3.21.2",
53
95
  "react": "~19.2.3",
54
96
  "react-dom": "~19.2.3",
55
- "@dxos/react-ui": "0.8.4-main.d05673bc65",
56
- "@dxos/ui-theme": "0.8.4-main.d05673bc65"
97
+ "@dxos/react-ui": "0.8.4-main.d9fc60f731",
98
+ "@dxos/ui-theme": "0.8.4-main.d9fc60f731"
57
99
  },
58
100
  "publishConfig": {
59
101
  "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
  );
@@ -6,12 +6,16 @@ import { RegistryContext, useAtomValue } from '@effect-atom/atom-react';
6
6
  import React, { type PropsWithChildren, useCallback, useContext, useEffect, useRef, useState } from 'react';
7
7
 
8
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';
9
11
  import {
10
12
  AlertDialog,
13
+ Button,
11
14
  Dialog,
12
15
  Main,
13
16
  Popover,
14
17
  type PopoverContentInteractOutsideEvent,
18
+ Toast,
15
19
  toLocalizedString,
16
20
  useTranslation,
17
21
  } from '@dxos/react-ui';
@@ -19,24 +23,53 @@ import { Card } from '@dxos/react-ui';
19
23
  import { Mosaic } from '@dxos/react-ui-mosaic';
20
24
  import { descriptionMessage, mx } from '@dxos/ui-theme';
21
25
 
22
- import { meta } from '../../meta';
23
- import { LayoutState, type LayoutStateProps } from '../../types';
26
+ import { meta } from '#meta';
27
+ import { StorybookCapabilities } from '#types';
24
28
 
25
29
  const debounce_delay = 100;
26
30
 
27
- // TODO(wittjosiah): Support dialogs, tooltips, maybe toast.
31
+ const StoryToast = ({ toast, onDismiss }: { toast: LayoutOperation.Toast; onDismiss: (id: string) => void }) => {
32
+ const { t } = useTranslation(meta.id);
33
+ return (
34
+ <Toast.Root
35
+ data-testid={toast.id}
36
+ defaultOpen
37
+ duration={toast.duration}
38
+ onOpenChange={(open) => {
39
+ if (!open) {
40
+ onDismiss(toast.id);
41
+ }
42
+ }}
43
+ >
44
+ <Toast.Title icon={toast.icon} onClose={toast.closeLabel ? () => onDismiss(toast.id) : undefined}>
45
+ {toast.title && <span>{toLocalizedString(toast.title, t)}</span>}
46
+ </Toast.Title>
47
+ {toast.description && <Toast.Description>{toLocalizedString(toast.description, t)}</Toast.Description>}
48
+ {toast.onAction && toast.actionAlt && toast.actionLabel && (
49
+ <Toast.Actions>
50
+ <Toast.Action altText={toLocalizedString(toast.actionAlt, t)} asChild>
51
+ <Button variant='primary' onClick={() => toast.onAction?.()}>
52
+ {toLocalizedString(toast.actionLabel, t)}
53
+ </Button>
54
+ </Toast.Action>
55
+ </Toast.Actions>
56
+ )}
57
+ </Toast.Root>
58
+ );
59
+ };
60
+
28
61
  export const Layout = ({ children }: PropsWithChildren<{}>) => {
29
62
  const { t } = useTranslation(meta.id);
30
63
  const trigger = useRef<HTMLButtonElement | null>(null);
31
64
  const registry = useContext(RegistryContext);
32
- const stateAtom = useCapability(LayoutState);
65
+ const stateAtom = useCapability(StorybookCapabilities.LayoutState);
33
66
  const layout = useAtomValue(stateAtom);
34
67
  const [iter, setIter] = useState(0);
35
68
  const [open, setOpen] = useState(false);
36
69
  const debounceRef = useRef<NodeJS.Timeout | null>(null);
37
70
 
38
71
  const updateState = useCallback(
39
- (updates: Partial<LayoutStateProps>) => {
72
+ (updates: Partial<StorybookCapabilities.LayoutStateProps>) => {
40
73
  const current = registry.get(stateAtom);
41
74
  registry.set(stateAtom, { ...current, ...updates });
42
75
  },
@@ -81,81 +114,100 @@ export const Layout = ({ children }: PropsWithChildren<{}>) => {
81
114
  [handleClose],
82
115
  );
83
116
 
117
+ const handleDismissToast = useCallback(
118
+ (id: string) => {
119
+ updateState({ toasts: layout.toasts.filter((toast) => toast.id !== id) });
120
+ },
121
+ [updateState, layout.toasts],
122
+ );
123
+
84
124
  const DialogRoot = layout.dialogType === 'alert' ? AlertDialog.Root : Dialog.Root;
85
125
  const DialogOverlay = layout.dialogType === 'alert' ? AlertDialog.Overlay : Dialog.Overlay;
86
126
 
87
127
  return (
88
- <div role='none' className='fixed inset-0 flex overflow-hidden'>
89
- <Mosaic.Root>
90
- <Popover.Root open={open}>
91
- <Main.Root
92
- navigationSidebarState={layout.sidebarState}
93
- complementarySidebarState={layout.complementarySidebarState}
94
- onNavigationSidebarStateChange={(next) => updateState({ sidebarState: next })}
95
- onComplementarySidebarStateChange={(next) => updateState({ complementarySidebarState: next })}
96
- >
97
- {children}
98
- </Main.Root>
99
-
100
- <DialogRoot
101
- modal={layout.dialogBlockAlign !== 'end'}
102
- open={layout.dialogOpen}
103
- onOpenChange={(nextOpen) => updateState({ dialogOpen: nextOpen })}
104
- >
105
- {layout.dialogBlockAlign === 'end' ? (
106
- <Surface.Surface
107
- role='dialog'
108
- data={layout.dialogContent}
109
- limit={1}
110
- fallback={ErrorFallback}
111
- placeholder={<div />}
112
- />
113
- ) : (
114
- <DialogOverlay
115
- blockAlign={layout.dialogBlockAlign}
116
- classNames={layout.dialogOverlayClasses}
117
- style={layout.dialogOverlayStyle}
118
- >
119
- <Surface.Surface role='dialog' data={layout.dialogContent} limit={1} fallback={ErrorFallback} />
120
- </DialogOverlay>
121
- )}
122
- </DialogRoot>
123
-
124
- <Popover.VirtualTrigger key={iter} virtualRef={trigger} />
125
- <Popover.Portal>
126
- <Popover.Content
127
- side={layout.popoverSide}
128
- onInteractOutside={handleInteractOutside}
129
- onEscapeKeyDown={handleInteractOutside}
130
- sticky='always'
131
- hideWhenDetached
128
+ <Toast.Provider>
129
+ <div className='fixed inset-0 flex overflow-hidden'>
130
+ <Mosaic.Root>
131
+ <Popover.Root open={open}>
132
+ <Main.Root
133
+ navigationSidebarState={layout.sidebarState}
134
+ complementarySidebarState={layout.complementarySidebarState}
135
+ onNavigationSidebarStateChange={(next) => updateState({ sidebarState: next })}
136
+ onComplementarySidebarStateChange={(next) => updateState({ complementarySidebarState: next })}
132
137
  >
133
- <Popover.Viewport>
134
- {layout.popoverKind === 'card' && (
135
- <Card.Root>
136
- <Card.Toolbar>
137
- {/* TODO(wittjosiah): Cleaner way to handle no drag handle in toolbar? */}
138
- <span />
139
- {layout.popoverTitle ? (
140
- <Card.Title>{toLocalizedString(layout.popoverTitle, t)}</Card.Title>
141
- ) : (
142
- <span />
143
- )}
144
- <Card.CloseIconButton onClick={handleClose} />
145
- </Card.Toolbar>
146
- <Surface.Surface role='card--content' data={layout.popoverContent} limit={1} />
147
- </Card.Root>
148
- )}
149
- {layout.popoverKind === 'base' && (
150
- <Surface.Surface role='popover' data={layout.popoverContent} limit={1} />
151
- )}
152
- </Popover.Viewport>
153
- <Popover.Arrow />
154
- </Popover.Content>
155
- </Popover.Portal>
156
- </Popover.Root>
157
- </Mosaic.Root>
158
- </div>
138
+ {children}
139
+ </Main.Root>
140
+
141
+ <DialogRoot
142
+ modal={layout.dialogBlockAlign !== 'end'}
143
+ open={layout.dialogOpen}
144
+ onOpenChange={(nextOpen) => updateState({ dialogOpen: nextOpen })}
145
+ >
146
+ {layout.dialogBlockAlign === 'end' ? (
147
+ <Surface.Surface
148
+ type={AppSurface.Dialog}
149
+ data={layout.dialogContent}
150
+ limit={1}
151
+ fallback={ErrorFallback}
152
+ placeholder={<div />}
153
+ />
154
+ ) : (
155
+ <DialogOverlay
156
+ blockAlign={layout.dialogBlockAlign}
157
+ classNames={layout.dialogOverlayClasses}
158
+ style={layout.dialogOverlayStyle}
159
+ >
160
+ <Surface.Surface
161
+ type={AppSurface.Dialog}
162
+ data={layout.dialogContent}
163
+ limit={1}
164
+ fallback={ErrorFallback}
165
+ />
166
+ </DialogOverlay>
167
+ )}
168
+ </DialogRoot>
169
+
170
+ <Popover.VirtualTrigger key={iter} virtualRef={trigger} />
171
+ <Popover.Portal>
172
+ <Popover.Content
173
+ side={layout.popoverSide}
174
+ onOpenAutoFocus={(event) => event.preventDefault()}
175
+ onInteractOutside={handleInteractOutside}
176
+ onEscapeKeyDown={handleInteractOutside}
177
+ sticky='always'
178
+ hideWhenDetached
179
+ >
180
+ <Popover.Viewport>
181
+ {layout.popoverKind === 'card' && (
182
+ <Card.Root>
183
+ <Card.Header>
184
+ {/* Disabled drag handle keeps the toolbar slot layout consistent with regular cards. */}
185
+ <Card.DragHandle />
186
+ {layout.popoverTitle ? (
187
+ <Card.Title>{toLocalizedString(layout.popoverTitle, t)}</Card.Title>
188
+ ) : (
189
+ <span />
190
+ )}
191
+ <Card.ActionIconButton action='close' onClick={handleClose} />
192
+ </Card.Header>
193
+ <Surface.Surface type={AppSurface.Card} data={layout.popoverContent} limit={1} />
194
+ </Card.Root>
195
+ )}
196
+ {layout.popoverKind === 'base' && (
197
+ <Surface.Surface type={AppSurface.Popover} data={layout.popoverContent} limit={1} />
198
+ )}
199
+ </Popover.Viewport>
200
+ <Popover.Arrow />
201
+ </Popover.Content>
202
+ </Popover.Portal>
203
+ </Popover.Root>
204
+ </Mosaic.Root>
205
+ {layout.toasts.map((toast) => (
206
+ <StoryToast key={toast.id} toast={toast} onDismiss={handleDismissToast} />
207
+ ))}
208
+ <Toast.Viewport />
209
+ </div>
210
+ </Toast.Provider>
159
211
  );
160
212
  };
161
213
 
@@ -169,7 +221,7 @@ export const ErrorFallback = ({ error }: { error?: Error }) => {
169
221
  className={mx('overflow-auto p-8 dx-attention-surface grid place-items-center')}
170
222
  >
171
223
  <p className={mx(descriptionMessage, 'break-words rounded-md p-8', errorString.length < 256 && 'text-lg')}>
172
- {error ? errorString : t('error fallback message')}
224
+ {error ? errorString : t('error-fallback.message')}
173
225
  </p>
174
226
  </div>
175
227
  );
package/src/core.ts CHANGED
@@ -2,17 +2,25 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
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';
11
- import { defaultTx } from '@dxos/ui-theme';
12
-
13
- // TODO(burdon): Remove this.
14
- // Re-export common framework plugins.
15
- export { AttentionPlugin, ClientPlugin, GraphPlugin, OperationPlugin, RuntimePlugin, SettingsPlugin, ThemePlugin };
5
+ import { type Plugin, ProcessManagerPlugin } from '@dxos/app-framework';
6
+ // `/testing` entrypoints re-export the plugin eagerly (without `Plugin.lazy`).
7
+ // The default `.` and `/plugin` exports wrap each plugin in a lazy stub
8
+ // (`() => import('./XPlugin')`), which webkit cannot reliably resolve under
9
+ // vite-dev: the dynamic-import promise can settle with a partially-evaluated
10
+ // namespace, throwing `ReferenceError: Cannot access 'default' before
11
+ // initialization` from the loader's `mod.default` access. Storybook runs
12
+ // inside `storybook dev` (vite-dev) and is currently the only host that hits
13
+ // this. Production hosts (composer-app via `vite preview`) keep using the
14
+ // lazy `.` exports and their associated code splitting.
15
+ //
16
+ // Use these `/testing` entrypoints from any storybook `withPluginManager`
17
+ // setup. The single-line `export * from './XPlugin'` re-exports avoid the
18
+ // dynamic-import path entirely — see e.g. `plugin-attention/src/testing.ts`.
19
+ import { AttentionPlugin } from '@dxos/plugin-attention/testing';
20
+ import { GraphPlugin } from '@dxos/plugin-graph/testing';
21
+ import { SettingsPlugin } from '@dxos/plugin-settings/testing';
22
+ import { ThemePlugin } from '@dxos/plugin-theme/testing';
23
+ import { defaultTx } from '@dxos/react-ui';
16
24
 
17
25
  /**
18
26
  * Core plugins for testing/storybook environments.
@@ -22,8 +30,7 @@ export { AttentionPlugin, ClientPlugin, GraphPlugin, OperationPlugin, RuntimePlu
22
30
  export const corePlugins = (): Plugin.Plugin[] => [
23
31
  AttentionPlugin(),
24
32
  GraphPlugin(),
25
- OperationPlugin(),
26
- RuntimePlugin(),
33
+ ProcessManagerPlugin(),
27
34
  SettingsPlugin(),
28
35
  ThemePlugin({ tx: defaultTx }),
29
36
  ];
package/src/harness.ts ADDED
@@ -0,0 +1,51 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type Plugin, ProcessManagerPlugin } from '@dxos/app-framework';
6
+ import { createTestApp, type TestAppOptions, type TestHarness } from '@dxos/app-framework/testing';
7
+ import { AttentionPlugin } from '@dxos/plugin-attention/testing';
8
+ import { GraphPlugin } from '@dxos/plugin-graph/testing';
9
+ import { SettingsPlugin } from '@dxos/plugin-settings/testing';
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
+ ProcessManagerPlugin(),
30
+ SettingsPlugin(),
31
+ ];
32
+
33
+ /**
34
+ * Creates a TestHarness pre-loaded with the Composer core plugins
35
+ * (Attention, Graph, ProcessManager, Settings, optionally Theme).
36
+ *
37
+ * For a ClientPlugin-backed harness, pass `ClientPlugin({ ... })` via `plugins`.
38
+ */
39
+ export const createComposerTestApp = async (opts: ComposerTestAppOptions = {}): Promise<TestHarness> => {
40
+ const { plugins = [], theme = false, ...rest } = opts;
41
+ const core = headlessCorePlugins();
42
+ if (theme) {
43
+ const { ThemePlugin } = await import('@dxos/plugin-theme/testing');
44
+ const { defaultTx } = await import('@dxos/react-ui');
45
+ core.push(ThemePlugin({ tx: defaultTx }));
46
+ }
47
+ return createTestApp({
48
+ ...rest,
49
+ plugins: [...core, ...plugins],
50
+ });
51
+ };
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
@@ -2,15 +2,17 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { type Plugin } from '@dxos/app-framework';
5
+ import { Plugin } from '@dxos/app-framework';
6
+ import { DXN } from '@dxos/keys';
6
7
  import { trim } from '@dxos/util';
7
8
 
8
- export const meta: Plugin.Meta = {
9
- id: 'org.dxos.plugin.storybook-layout',
9
+ export const meta = Plugin.makeMeta({
10
+ key: DXN.make('org.dxos.plugin.storybookLayout'),
10
11
  name: 'Storybook',
12
+ author: 'DXOS',
11
13
  description: trim`
12
14
  Development layout optimized for Storybook component testing and documentation.
13
15
  Provides specialized views for component development and design system exploration.
14
16
  `,
15
17
  source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-testing',
16
- };
18
+ });
@@ -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;