@dxos/plugin-testing 0.8.4-main.bc674ce → 0.8.4-main.bcb3aa67d6

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 (93) hide show
  1. package/dist/lib/browser/index.mjs +30 -172
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +30 -171
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/StorybookPlugin.d.ts +1 -1
  8. package/dist/types/src/StorybookPlugin.d.ts.map +1 -1
  9. package/dist/types/src/capabilities/index.d.ts +15 -2
  10. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  11. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  12. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  13. package/dist/types/src/capabilities/{state/state.d.ts → state.d.ts} +2 -2
  14. package/dist/types/src/capabilities/state.d.ts.map +1 -0
  15. package/dist/types/src/components/{Layout.d.ts → Layout/Layout.d.ts} +1 -1
  16. package/dist/types/src/components/Layout/Layout.d.ts.map +1 -0
  17. package/dist/types/src/components/Layout/index.d.ts +2 -0
  18. package/dist/types/src/components/Layout/index.d.ts.map +1 -0
  19. package/dist/types/src/core.d.ts +1 -6
  20. package/dist/types/src/core.d.ts.map +1 -1
  21. package/dist/types/src/operations/add-toast.d.ts +5 -0
  22. package/dist/types/src/operations/add-toast.d.ts.map +1 -0
  23. package/dist/types/src/operations/close.d.ts +5 -0
  24. package/dist/types/src/operations/close.d.ts.map +1 -0
  25. package/dist/types/src/operations/index.d.ts +3 -0
  26. package/dist/types/src/operations/index.d.ts.map +1 -0
  27. package/dist/types/src/operations/open.d.ts +5 -0
  28. package/dist/types/src/operations/open.d.ts.map +1 -0
  29. package/dist/types/src/operations/scroll-into-view.d.ts +5 -0
  30. package/dist/types/src/operations/scroll-into-view.d.ts.map +1 -0
  31. package/dist/types/src/operations/set-layout-mode.d.ts +5 -0
  32. package/dist/types/src/operations/set-layout-mode.d.ts.map +1 -0
  33. package/dist/types/src/operations/switch-workspace.d.ts +5 -0
  34. package/dist/types/src/operations/switch-workspace.d.ts.map +1 -0
  35. package/dist/types/src/operations/update-complementary.d.ts +5 -0
  36. package/dist/types/src/operations/update-complementary.d.ts.map +1 -0
  37. package/dist/types/src/operations/update-dialog.d.ts +5 -0
  38. package/dist/types/src/operations/update-dialog.d.ts.map +1 -0
  39. package/dist/types/src/operations/update-popover.d.ts +5 -0
  40. package/dist/types/src/operations/update-popover.d.ts.map +1 -0
  41. package/dist/types/src/operations/update-sidebar.d.ts +5 -0
  42. package/dist/types/src/operations/update-sidebar.d.ts.map +1 -0
  43. package/dist/types/src/operations/update-state.d.ts +5 -0
  44. package/dist/types/src/operations/update-state.d.ts.map +1 -0
  45. package/dist/types/src/types/capabilities.d.ts +2 -0
  46. package/dist/types/src/types/capabilities.d.ts.map +1 -1
  47. package/dist/types/tsconfig.tsbuildinfo +1 -1
  48. package/package.json +26 -18
  49. package/src/StorybookPlugin.ts +18 -16
  50. package/src/capabilities/index.ts +8 -2
  51. package/src/capabilities/operation-handler.ts +16 -0
  52. package/src/capabilities/{state/state.tsx → state.tsx} +6 -7
  53. package/src/components/Layout/Layout.tsx +230 -0
  54. package/src/components/Layout/index.ts +5 -0
  55. package/src/core.ts +2 -6
  56. package/src/meta.ts +1 -1
  57. package/src/operations/add-toast.ts +22 -0
  58. package/src/operations/close.ts +14 -0
  59. package/src/operations/index.ts +18 -0
  60. package/src/operations/open.ts +18 -0
  61. package/src/operations/scroll-into-view.ts +14 -0
  62. package/src/operations/set-layout-mode.ts +14 -0
  63. package/src/operations/switch-workspace.ts +20 -0
  64. package/src/operations/update-complementary.ts +27 -0
  65. package/src/operations/update-dialog.ts +27 -0
  66. package/src/operations/update-popover.ts +37 -0
  67. package/src/operations/update-sidebar.ts +26 -0
  68. package/src/operations/update-state.ts +17 -0
  69. package/src/types/capabilities.ts +5 -2
  70. package/dist/lib/browser/chunk-YHPXIILW.mjs +0 -21
  71. package/dist/lib/browser/chunk-YHPXIILW.mjs.map +0 -7
  72. package/dist/lib/browser/operation-resolver-B2DOYB7C.mjs +0 -111
  73. package/dist/lib/browser/operation-resolver-B2DOYB7C.mjs.map +0 -7
  74. package/dist/lib/browser/state-2M3RMJYA.mjs +0 -42
  75. package/dist/lib/browser/state-2M3RMJYA.mjs.map +0 -7
  76. package/dist/lib/node-esm/chunk-OWK6XE6C.mjs +0 -23
  77. package/dist/lib/node-esm/chunk-OWK6XE6C.mjs.map +0 -7
  78. package/dist/lib/node-esm/operation-resolver-DJI7OPBP.mjs +0 -112
  79. package/dist/lib/node-esm/operation-resolver-DJI7OPBP.mjs.map +0 -7
  80. package/dist/lib/node-esm/state-UF2MWBFU.mjs +0 -43
  81. package/dist/lib/node-esm/state-UF2MWBFU.mjs.map +0 -7
  82. package/dist/types/src/capabilities/operation-resolver/index.d.ts +0 -3
  83. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +0 -1
  84. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +0 -5
  85. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +0 -1
  86. package/dist/types/src/capabilities/state/index.d.ts +0 -14
  87. package/dist/types/src/capabilities/state/index.d.ts.map +0 -1
  88. package/dist/types/src/capabilities/state/state.d.ts.map +0 -1
  89. package/dist/types/src/components/Layout.d.ts.map +0 -1
  90. package/src/capabilities/operation-resolver/index.ts +0 -7
  91. package/src/capabilities/operation-resolver/operation-resolver.ts +0 -98
  92. package/src/capabilities/state/index.ts +0 -7
  93. package/src/components/Layout.tsx +0 -172
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-testing",
3
- "version": "0.8.4-main.bc674ce",
3
+ "version": "0.8.4-main.bcb3aa67d6",
4
4
  "description": "Plugin testing utils",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -12,6 +12,13 @@
12
12
  "author": "DXOS.org",
13
13
  "sideEffects": true,
14
14
  "type": "module",
15
+ "imports": {
16
+ "#capabilities": "./src/capabilities/index.ts",
17
+ "#components": "./src/components/index.ts",
18
+ "#meta": "./src/meta.ts",
19
+ "#operations": "./src/operations/index.ts",
20
+ "#types": "./src/types/index.ts"
21
+ },
15
22
  "exports": {
16
23
  ".": {
17
24
  "source": "./src/index.ts",
@@ -25,33 +32,34 @@
25
32
  "src"
26
33
  ],
27
34
  "dependencies": {
28
- "@effect-atom/atom": "^0.4.13",
29
- "@effect-atom/atom-react": "^0.4.6",
30
- "@dxos/app-framework": "0.8.4-main.bc674ce",
31
- "@dxos/operation": "0.8.4-main.bc674ce",
32
- "@dxos/plugin-attention": "0.8.4-main.bc674ce",
33
- "@dxos/plugin-client": "0.8.4-main.bc674ce",
34
- "@dxos/plugin-graph": "0.8.4-main.bc674ce",
35
- "@dxos/plugin-theme": "0.8.4-main.bc674ce",
36
- "@dxos/react-ui-mosaic": "0.8.4-main.bc674ce",
37
- "@dxos/util": "0.8.4-main.bc674ce"
35
+ "@effect-atom/atom": "^0.5.1",
36
+ "@effect-atom/atom-react": "^0.5.0",
37
+ "@dxos/app-framework": "0.8.4-main.bcb3aa67d6",
38
+ "@dxos/app-toolkit": "0.8.4-main.bcb3aa67d6",
39
+ "@dxos/operation": "0.8.4-main.bcb3aa67d6",
40
+ "@dxos/plugin-graph": "0.8.4-main.bcb3aa67d6",
41
+ "@dxos/plugin-settings": "0.8.4-main.bcb3aa67d6",
42
+ "@dxos/plugin-attention": "0.8.4-main.bcb3aa67d6",
43
+ "@dxos/plugin-theme": "0.8.4-main.bcb3aa67d6",
44
+ "@dxos/react-ui-mosaic": "0.8.4-main.bcb3aa67d6",
45
+ "@dxos/util": "0.8.4-main.bcb3aa67d6"
38
46
  },
39
47
  "devDependencies": {
40
48
  "@types/react": "~19.2.7",
41
49
  "@types/react-dom": "~19.2.3",
42
- "effect": "3.19.11",
50
+ "effect": "3.20.0",
43
51
  "react": "~19.2.3",
44
52
  "react-dom": "~19.2.3",
45
- "vite": "7.1.9",
46
- "@dxos/react-ui": "0.8.4-main.bc674ce",
47
- "@dxos/ui-theme": "0.8.4-main.bc674ce"
53
+ "vite": "^7.1.11",
54
+ "@dxos/react-ui": "0.8.4-main.bcb3aa67d6",
55
+ "@dxos/ui-theme": "0.8.4-main.bcb3aa67d6"
48
56
  },
49
57
  "peerDependencies": {
50
- "effect": "3.19.11",
58
+ "effect": "3.20.0",
51
59
  "react": "~19.2.3",
52
60
  "react-dom": "~19.2.3",
53
- "@dxos/react-ui": "0.8.4-main.bc674ce",
54
- "@dxos/ui-theme": "0.8.4-main.bc674ce"
61
+ "@dxos/react-ui": "0.8.4-main.bcb3aa67d6",
62
+ "@dxos/ui-theme": "0.8.4-main.bcb3aa67d6"
55
63
  },
56
64
  "publishConfig": {
57
65
  "access": "public"
@@ -4,35 +4,37 @@
4
4
 
5
5
  import * as Effect from 'effect/Effect';
6
6
 
7
- import { Capability, Common, Plugin } from '@dxos/app-framework';
7
+ import { ActivationEvents, Capabilities, Capability, Plugin } from '@dxos/app-framework';
8
+ import { AppActivationEvents, AppPlugin } from '@dxos/app-toolkit';
8
9
 
9
- import { OperationResolver, State } from './capabilities';
10
- import { Layout } from './components';
11
- import { meta } from './meta';
12
- import { type LayoutStateProps } from './types';
10
+ import { Layout } from '#components';
11
+ import { meta } from '#meta';
12
+ import { type LayoutStateProps } from '#types';
13
+
14
+ import { OperationHandler, State } from '#capabilities';
13
15
 
14
16
  export type StorybookPluginOptions = {
15
17
  initialState?: Partial<LayoutStateProps>;
16
18
  };
17
19
 
18
20
  export const StorybookPlugin = Plugin.define<StorybookPluginOptions>(meta).pipe(
19
- Plugin.addModule(({ initialState }) => ({
20
- id: Capability.getModuleTag(State),
21
- activatesOn: Common.ActivationEvent.Startup,
22
- activatesAfter: [Common.ActivationEvent.LayoutReady],
23
- activate: () => State({ initialState }),
24
- })),
25
- Common.Plugin.addReactContextModule({
21
+ AppPlugin.addOperationHandlerModule({
22
+ activate: OperationHandler,
23
+ }),
24
+ AppPlugin.addReactContextModule({
26
25
  activate: () =>
27
26
  Effect.succeed(
28
- Capability.contributes(Common.Capability.ReactContext, {
27
+ Capability.contributes(Capabilities.ReactContext, {
29
28
  id: 'storybook-layout',
30
29
  context: Layout,
31
30
  }),
32
31
  ),
33
32
  }),
34
- Common.Plugin.addOperationResolverModule({
35
- activate: OperationResolver,
36
- }),
33
+ Plugin.addModule(({ initialState }) => ({
34
+ id: Capability.getModuleTag(State),
35
+ activatesOn: ActivationEvents.Startup,
36
+ activatesAfter: [AppActivationEvents.LayoutReady],
37
+ activate: () => State({ initialState }),
38
+ })),
37
39
  Plugin.make,
38
40
  );
@@ -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/operation';
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/operation';
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
+ );
@@ -5,14 +5,16 @@
5
5
  import { Atom } from '@effect-atom/atom-react';
6
6
  import * as Effect from 'effect/Effect';
7
7
 
8
- import { Capability, Common } from '@dxos/app-framework';
8
+ import { Capability } from '@dxos/app-framework';
9
+ import { AppCapabilities } from '@dxos/app-toolkit';
9
10
 
10
- import { LayoutState, type LayoutStateProps } from '../../types';
11
+ import { LayoutState, type LayoutStateProps } from '#types';
11
12
 
12
13
  const defaultState: LayoutStateProps = {
13
14
  sidebarState: 'closed',
14
15
  complementarySidebarState: 'closed',
15
16
  dialogOpen: false,
17
+ toasts: [],
16
18
  workspace: 'default',
17
19
  };
18
20
 
@@ -21,7 +23,7 @@ export default Capability.makeModule(
21
23
  const { initialState } = props ?? {};
22
24
  const stateAtom = Atom.make<LayoutStateProps>({ ...defaultState, ...initialState });
23
25
 
24
- const layoutAtom = Atom.make((get): Common.Capability.Layout => {
26
+ const layoutAtom = Atom.make((get): AppCapabilities.Layout => {
25
27
  const state = get(stateAtom);
26
28
  return {
27
29
  mode: 'storybook',
@@ -35,9 +37,6 @@ export default Capability.makeModule(
35
37
  };
36
38
  });
37
39
 
38
- return [
39
- Capability.contributes(LayoutState, stateAtom),
40
- Capability.contributes(Common.Capability.Layout, layoutAtom),
41
- ];
40
+ return [Capability.contributes(LayoutState, stateAtom), Capability.contributes(AppCapabilities.Layout, layoutAtom)];
42
41
  }),
43
42
  );
@@ -0,0 +1,230 @@
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 {
11
+ AlertDialog,
12
+ Button,
13
+ Dialog,
14
+ Icon,
15
+ Main,
16
+ Popover,
17
+ type PopoverContentInteractOutsideEvent,
18
+ Toast,
19
+ toLocalizedString,
20
+ useTranslation,
21
+ } from '@dxos/react-ui';
22
+ import { Card } from '@dxos/react-ui';
23
+ import { Mosaic } from '@dxos/react-ui-mosaic';
24
+ import { descriptionMessage, mx } from '@dxos/ui-theme';
25
+
26
+ import { meta } from '#meta';
27
+ import { LayoutState, type LayoutStateProps } from '#types';
28
+
29
+ const debounce_delay = 100;
30
+
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.Body>
45
+ <Toast.Title classNames='items-center'>
46
+ {toast.icon && <Icon icon={toast.icon} classNames='inline mr-1' />}
47
+ {toast.title && <span>{toLocalizedString(toast.title, t)}</span>}
48
+ </Toast.Title>
49
+ {toast.description && <Toast.Description>{toLocalizedString(toast.description, t)}</Toast.Description>}
50
+ </Toast.Body>
51
+ <Toast.Actions>
52
+ {toast.onAction && toast.actionAlt && toast.actionLabel && (
53
+ <Toast.Action altText={toLocalizedString(toast.actionAlt, t)} asChild>
54
+ <Button variant='primary' onClick={() => toast.onAction?.()}>
55
+ {toLocalizedString(toast.actionLabel, t)}
56
+ </Button>
57
+ </Toast.Action>
58
+ )}
59
+ {toast.closeLabel && (
60
+ <Toast.Close asChild>
61
+ <Button>{toLocalizedString(toast.closeLabel, t)}</Button>
62
+ </Toast.Close>
63
+ )}
64
+ </Toast.Actions>
65
+ </Toast.Root>
66
+ );
67
+ };
68
+
69
+ export const Layout = ({ children }: PropsWithChildren<{}>) => {
70
+ const { t } = useTranslation(meta.id);
71
+ const trigger = useRef<HTMLButtonElement | null>(null);
72
+ const registry = useContext(RegistryContext);
73
+ const stateAtom = useCapability(LayoutState);
74
+ const layout = useAtomValue(stateAtom);
75
+ const [iter, setIter] = useState(0);
76
+ const [open, setOpen] = useState(false);
77
+ const debounceRef = useRef<NodeJS.Timeout | null>(null);
78
+
79
+ const updateState = useCallback(
80
+ (updates: Partial<LayoutStateProps>) => {
81
+ const current = registry.get(stateAtom);
82
+ registry.set(stateAtom, { ...current, ...updates });
83
+ },
84
+ [registry, stateAtom],
85
+ );
86
+
87
+ useEffect(() => {
88
+ setOpen(false);
89
+ if (debounceRef.current) {
90
+ clearTimeout(debounceRef.current);
91
+ debounceRef.current = null;
92
+ }
93
+ trigger.current = layout.popoverAnchor ?? null;
94
+ setIter((iter) => iter + 1);
95
+ if (layout.popoverOpen) {
96
+ debounceRef.current = setTimeout(() => setOpen(true), debounce_delay);
97
+ }
98
+ }, [layout.popoverAnchor, layout.popoverContent, layout.popoverOpen]);
99
+
100
+ const handleClose = useCallback(() => {
101
+ setOpen(false);
102
+ updateState({
103
+ popoverOpen: false,
104
+ popoverAnchor: undefined,
105
+ popoverAnchorId: undefined,
106
+ popoverSide: undefined,
107
+ });
108
+ }, [updateState]);
109
+
110
+ const handleInteractOutside = useCallback(
111
+ (event: KeyboardEvent | PopoverContentInteractOutsideEvent) => {
112
+ if (
113
+ // TODO(thure): CodeMirror should not focus itself when it updates.
114
+ event.type === 'dismissableLayer.focusOutside' &&
115
+ (event.currentTarget as HTMLElement | undefined)?.classList.contains('cm-content')
116
+ ) {
117
+ event.preventDefault();
118
+ } else {
119
+ handleClose();
120
+ }
121
+ },
122
+ [handleClose],
123
+ );
124
+
125
+ const handleDismissToast = useCallback(
126
+ (id: string) => {
127
+ updateState({ toasts: layout.toasts.filter((toast) => toast.id !== id) });
128
+ },
129
+ [updateState, layout.toasts],
130
+ );
131
+
132
+ const DialogRoot = layout.dialogType === 'alert' ? AlertDialog.Root : Dialog.Root;
133
+ const DialogOverlay = layout.dialogType === 'alert' ? AlertDialog.Overlay : Dialog.Overlay;
134
+
135
+ return (
136
+ <Toast.Provider>
137
+ <div role='none' className='fixed inset-0 flex overflow-hidden'>
138
+ <Mosaic.Root>
139
+ <Popover.Root open={open}>
140
+ <Main.Root
141
+ navigationSidebarState={layout.sidebarState}
142
+ complementarySidebarState={layout.complementarySidebarState}
143
+ onNavigationSidebarStateChange={(next) => updateState({ sidebarState: next })}
144
+ onComplementarySidebarStateChange={(next) => updateState({ complementarySidebarState: next })}
145
+ >
146
+ {children}
147
+ </Main.Root>
148
+
149
+ <DialogRoot
150
+ modal={layout.dialogBlockAlign !== 'end'}
151
+ open={layout.dialogOpen}
152
+ onOpenChange={(nextOpen) => updateState({ dialogOpen: nextOpen })}
153
+ >
154
+ {layout.dialogBlockAlign === 'end' ? (
155
+ <Surface.Surface
156
+ role='dialog'
157
+ data={layout.dialogContent}
158
+ limit={1}
159
+ fallback={ErrorFallback}
160
+ placeholder={<div />}
161
+ />
162
+ ) : (
163
+ <DialogOverlay
164
+ blockAlign={layout.dialogBlockAlign}
165
+ classNames={layout.dialogOverlayClasses}
166
+ style={layout.dialogOverlayStyle}
167
+ >
168
+ <Surface.Surface role='dialog' data={layout.dialogContent} limit={1} fallback={ErrorFallback} />
169
+ </DialogOverlay>
170
+ )}
171
+ </DialogRoot>
172
+
173
+ <Popover.VirtualTrigger key={iter} virtualRef={trigger} />
174
+ <Popover.Portal>
175
+ <Popover.Content
176
+ side={layout.popoverSide}
177
+ onInteractOutside={handleInteractOutside}
178
+ onEscapeKeyDown={handleInteractOutside}
179
+ sticky='always'
180
+ hideWhenDetached
181
+ >
182
+ <Popover.Viewport>
183
+ {layout.popoverKind === 'card' && (
184
+ <Card.Root>
185
+ <Card.Toolbar>
186
+ {/* TODO(wittjosiah): Cleaner way to handle no drag handle in toolbar? */}
187
+ <span />
188
+ {layout.popoverTitle ? (
189
+ <Card.Title>{toLocalizedString(layout.popoverTitle, t)}</Card.Title>
190
+ ) : (
191
+ <span />
192
+ )}
193
+ <Card.CloseIconButton onClick={handleClose} />
194
+ </Card.Toolbar>
195
+ <Surface.Surface role='card--content' data={layout.popoverContent} limit={1} />
196
+ </Card.Root>
197
+ )}
198
+ {layout.popoverKind === 'base' && (
199
+ <Surface.Surface role='popover' data={layout.popoverContent} limit={1} />
200
+ )}
201
+ </Popover.Viewport>
202
+ <Popover.Arrow />
203
+ </Popover.Content>
204
+ </Popover.Portal>
205
+ </Popover.Root>
206
+ </Mosaic.Root>
207
+ {layout.toasts.map((toast) => (
208
+ <StoryToast key={toast.id} toast={toast} onDismiss={handleDismissToast} />
209
+ ))}
210
+ <Toast.Viewport />
211
+ </div>
212
+ </Toast.Provider>
213
+ );
214
+ };
215
+
216
+ export const ErrorFallback = ({ error }: { error?: Error }) => {
217
+ const { t } = useTranslation(meta.id);
218
+ const errorString = error?.toString() ?? '';
219
+ return (
220
+ <div
221
+ role='alert'
222
+ data-testid='error-boundary-fallback'
223
+ className={mx('overflow-auto p-8 dx-attention-surface grid place-items-center')}
224
+ >
225
+ <p className={mx(descriptionMessage, 'break-words rounded-md p-8', errorString.length < 256 && 'text-lg')}>
226
+ {error ? errorString : t('error-fallback.message')}
227
+ </p>
228
+ </div>
229
+ );
230
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './Layout';
package/src/core.ts CHANGED
@@ -2,17 +2,13 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { OperationPlugin, type Plugin, RuntimePlugin, SettingsPlugin } from '@dxos/app-framework';
5
+ import { OperationPlugin, type Plugin, RuntimePlugin } from '@dxos/app-framework';
6
6
  import { AttentionPlugin } from '@dxos/plugin-attention';
7
- import { ClientPlugin } from '@dxos/plugin-client';
8
7
  import { GraphPlugin } from '@dxos/plugin-graph';
8
+ import { SettingsPlugin } from '@dxos/plugin-settings';
9
9
  import { ThemePlugin } from '@dxos/plugin-theme';
10
10
  import { defaultTx } from '@dxos/ui-theme';
11
11
 
12
- // TODO(burdon): Remove this.
13
- // Re-export common framework plugins.
14
- export { AttentionPlugin, ClientPlugin, GraphPlugin, OperationPlugin, RuntimePlugin, SettingsPlugin, ThemePlugin };
15
-
16
12
  /**
17
13
  * Core plugins for testing/storybook environments.
18
14
  * NOTE: Does not include SpacePlugin to avoid circular dependencies.
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/operation';
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/operation';
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/operation';
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/operation';
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/operation';
9
+
10
+ const handler: Operation.WithHandler<typeof LayoutOperation.ScrollIntoView> = LayoutOperation.ScrollIntoView.pipe(
11
+ Operation.withHandler(() => Effect.void),
12
+ );
13
+
14
+ 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/operation';
9
+
10
+ const handler: Operation.WithHandler<typeof LayoutOperation.SetLayoutMode> = LayoutOperation.SetLayoutMode.pipe(
11
+ Operation.withHandler(() => Effect.void),
12
+ );
13
+
14
+ export default handler;
@@ -0,0 +1,20 @@
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/operation';
9
+
10
+ import { updateState } from './update-state';
11
+
12
+ const handler: Operation.WithHandler<typeof LayoutOperation.SwitchWorkspace> = LayoutOperation.SwitchWorkspace.pipe(
13
+ Operation.withHandler(
14
+ Effect.fnUntraced(function* ({ subject }) {
15
+ yield* updateState(() => ({ workspace: subject }));
16
+ }),
17
+ ),
18
+ );
19
+
20
+ export default handler;
@@ -0,0 +1,27 @@
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/operation';
9
+
10
+ import { updateState } from './update-state';
11
+
12
+ const handler: Operation.WithHandler<typeof LayoutOperation.UpdateComplementary> =
13
+ LayoutOperation.UpdateComplementary.pipe(
14
+ Operation.withHandler(
15
+ Effect.fnUntraced(function* ({ state }) {
16
+ yield* updateState((layout) => {
17
+ const next = state ?? layout.complementarySidebarState;
18
+ if (next !== layout.complementarySidebarState) {
19
+ return { complementarySidebarState: next };
20
+ }
21
+ return {};
22
+ });
23
+ }),
24
+ ),
25
+ );
26
+
27
+ export default handler;
@@ -0,0 +1,27 @@
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/operation';
9
+
10
+ import { updateState } from './update-state';
11
+
12
+ const handler: Operation.WithHandler<typeof LayoutOperation.UpdateDialog> = LayoutOperation.UpdateDialog.pipe(
13
+ Operation.withHandler(
14
+ Effect.fnUntraced(function* ({ subject, state, type, blockAlign, overlayClasses, overlayStyle, props }) {
15
+ yield* updateState(() => ({
16
+ dialogOpen: state ?? Boolean(subject),
17
+ dialogType: type ?? 'default',
18
+ dialogBlockAlign: blockAlign ?? 'center',
19
+ dialogOverlayClasses: overlayClasses,
20
+ dialogOverlayStyle: overlayStyle,
21
+ dialogContent: subject ? { component: subject, props } : null,
22
+ }));
23
+ }),
24
+ ),
25
+ );
26
+
27
+ export default handler;