@dxos/plugin-testing 0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264 → 0.13.0

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 (87) hide show
  1. package/dist/lib/StorybookCapabilities.mjs.map +1 -1
  2. package/dist/lib/capabilities.browser.mjs +16 -0
  3. package/dist/lib/capabilities.browser.mjs.map +1 -0
  4. package/dist/lib/capabilities.mjs.map +1 -1
  5. package/dist/lib/capabilities.node.mjs.map +1 -1
  6. package/dist/lib/capabilities.tauri.mjs +16 -0
  7. package/dist/lib/capabilities.tauri.mjs.map +1 -0
  8. package/dist/lib/capabilities.workerd.mjs.map +1 -1
  9. package/dist/lib/chunk-add-toast.mjs.map +1 -1
  10. package/dist/lib/chunk-state.mjs +1 -1
  11. package/dist/lib/chunk-state.mjs.map +1 -1
  12. package/dist/lib/chunk-switch-workspace.mjs.map +1 -1
  13. package/dist/lib/chunk-update-complementary.mjs.map +1 -1
  14. package/dist/lib/chunk-update-dialog.mjs.map +1 -1
  15. package/dist/lib/chunk-update-popover.mjs.map +1 -1
  16. package/dist/lib/chunk-update-sidebar.mjs.map +1 -1
  17. package/dist/lib/components.mjs +62 -55
  18. package/dist/lib/components.mjs.map +1 -1
  19. package/dist/lib/core.mjs +4 -4
  20. package/dist/lib/core.mjs.map +1 -1
  21. package/dist/lib/harness.mjs +7 -5
  22. package/dist/lib/harness.mjs.map +1 -1
  23. package/dist/lib/index.mjs +4 -214
  24. package/dist/lib/meta.mjs.map +1 -1
  25. package/dist/lib/ns/CorePlugins.mjs +8 -0
  26. package/dist/lib/ns/CorePlugins.mjs.map +1 -0
  27. package/dist/lib/ns/Corpus.mjs +216 -0
  28. package/dist/lib/ns/Corpus.mjs.map +1 -0
  29. package/dist/lib/operations.mjs.map +1 -1
  30. package/dist/types/src/CorePlugins.d.ts +2 -0
  31. package/dist/types/src/CorePlugins.d.ts.map +1 -0
  32. package/dist/types/src/Corpus.d.ts +2 -0
  33. package/dist/types/src/Corpus.d.ts.map +1 -0
  34. package/dist/types/src/{harness.d.ts → Harness.d.ts} +1 -1
  35. package/dist/types/src/{harness.d.ts.map → Harness.d.ts.map} +1 -1
  36. package/dist/types/src/capabilities/gen/browser.d.ts +8 -0
  37. package/dist/types/src/capabilities/gen/browser.d.ts.map +1 -0
  38. package/dist/types/src/capabilities/gen/node.d.ts.map +1 -1
  39. package/dist/types/src/capabilities/gen/tauri.d.ts +8 -0
  40. package/dist/types/src/capabilities/gen/tauri.d.ts.map +1 -0
  41. package/dist/types/src/capabilities/gen/workerd.d.ts.map +1 -1
  42. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  43. package/dist/types/src/components/Layout/Layout.d.ts.map +1 -1
  44. package/dist/types/src/components/Layout/index.d.ts +1 -1
  45. package/dist/types/src/components/Layout/index.d.ts.map +1 -1
  46. package/dist/types/src/components/index.d.ts +1 -1
  47. package/dist/types/src/components/index.d.ts.map +1 -1
  48. package/dist/types/src/core.d.ts.map +1 -1
  49. package/dist/types/src/corpus/index.d.ts +1 -1
  50. package/dist/types/src/corpus/index.d.ts.map +1 -1
  51. package/dist/types/src/index.d.ts +4 -4
  52. package/dist/types/src/index.d.ts.map +1 -1
  53. package/dist/types/src/operations/update-state.d.ts +1 -1
  54. package/dist/types/src/operations/update-state.d.ts.map +1 -1
  55. package/dist/types/src/types/StorybookCapabilities.d.ts +3 -3
  56. package/dist/types/src/types/StorybookCapabilities.d.ts.map +1 -1
  57. package/dist/types/src/types/index.d.ts +1 -1
  58. package/dist/types/src/types/index.d.ts.map +1 -1
  59. package/dist/types/tsconfig.tsbuildinfo +1 -1
  60. package/package.json +37 -25
  61. package/src/CorePlugins.ts +7 -0
  62. package/src/Corpus.ts +7 -0
  63. package/src/{harness.ts → Harness.ts} +5 -3
  64. package/src/capabilities/gen/browser.ts +23 -0
  65. package/src/capabilities/gen/node.ts +2 -2
  66. package/src/capabilities/gen/tauri.ts +23 -0
  67. package/src/capabilities/gen/workerd.ts +2 -2
  68. package/src/capabilities/index.ts +3 -3
  69. package/src/capabilities/state.tsx +1 -1
  70. package/src/components/Layout/Layout.tsx +101 -96
  71. package/src/components/Layout/index.ts +1 -1
  72. package/src/components/index.ts +1 -1
  73. package/src/core.ts +4 -4
  74. package/src/corpus/index.ts +1 -1
  75. package/src/corpus/sample-messages.test.ts +1 -1
  76. package/src/index.ts +4 -4
  77. package/src/meta.ts +1 -1
  78. package/src/operations/add-toast.ts +1 -1
  79. package/src/operations/index.ts +9 -9
  80. package/src/operations/switch-workspace.ts +1 -1
  81. package/src/operations/update-complementary.ts +1 -1
  82. package/src/operations/update-dialog.ts +1 -1
  83. package/src/operations/update-popover.ts +1 -1
  84. package/src/operations/update-sidebar.ts +1 -1
  85. package/src/types/StorybookCapabilities.ts +3 -3
  86. package/src/types/index.ts +1 -1
  87. package/dist/lib/index.mjs.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-testing",
3
- "version": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
3
+ "version": "0.13.0",
4
4
  "description": "Plugin testing utils",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -10,19 +10,21 @@
10
10
  },
11
11
  "license": "FSL-1.1-Apache-2.0",
12
12
  "author": "DXOS.org",
13
- "sideEffects": true,
13
+ "sideEffects": false,
14
14
  "type": "module",
15
15
  "imports": {
16
16
  "#capabilities": {
17
17
  "source": {
18
18
  "workerd": "./src/capabilities/gen/workerd.ts",
19
19
  "node": "./src/capabilities/gen/node.ts",
20
- "default": "./src/capabilities/index.ts"
20
+ "tauri": "./src/capabilities/gen/tauri.ts",
21
+ "browser": "./src/capabilities/gen/browser.ts"
21
22
  },
22
23
  "types": "./dist/types/src/capabilities/index.d.ts",
23
24
  "workerd": "./dist/lib/capabilities.workerd.mjs",
24
25
  "node": "./dist/lib/capabilities.node.mjs",
25
- "default": "./dist/lib/capabilities.mjs"
26
+ "tauri": "./dist/lib/capabilities.tauri.mjs",
27
+ "browser": "./dist/lib/capabilities.browser.mjs"
26
28
  },
27
29
  "#components": {
28
30
  "source": "./src/components/index.ts",
@@ -56,9 +58,19 @@
56
58
  "types": "./dist/types/src/index.d.ts",
57
59
  "import": "./dist/lib/index.mjs"
58
60
  },
59
- "./harness": {
60
- "source": "./src/harness.ts",
61
- "types": "./dist/types/src/harness.d.ts",
61
+ "./CorePlugins": {
62
+ "source": "./src/CorePlugins.ts",
63
+ "types": "./dist/types/src/CorePlugins.d.ts",
64
+ "import": "./dist/lib/ns/CorePlugins.mjs"
65
+ },
66
+ "./Corpus": {
67
+ "source": "./src/Corpus.ts",
68
+ "types": "./dist/types/src/Corpus.d.ts",
69
+ "import": "./dist/lib/ns/Corpus.mjs"
70
+ },
71
+ "./Harness": {
72
+ "source": "./src/Harness.ts",
73
+ "types": "./dist/types/src/Harness.d.ts",
62
74
  "import": "./dist/lib/harness.mjs"
63
75
  },
64
76
  "./package.json": "./package.json",
@@ -87,34 +99,34 @@
87
99
  "src"
88
100
  ],
89
101
  "dependencies": {
90
- "@effect/atom-react": "4.0.0-rc.108",
91
- "@dxos/app-framework": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
92
- "@dxos/app-toolkit": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
93
- "@dxos/log": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
94
- "@dxos/compute": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
95
- "@dxos/plugin-attention": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
96
- "@dxos/plugin-graph": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
97
- "@dxos/plugin-settings": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
98
- "@dxos/plugin-theme": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
99
- "@dxos/react-ui-dnd": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
100
- "@dxos/util": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264"
102
+ "@effect/atom-react": "4.0.0",
103
+ "@dxos/app-framework": "0.13.0",
104
+ "@dxos/app-toolkit": "0.13.0",
105
+ "@dxos/compute": "0.13.0",
106
+ "@dxos/log": "0.13.0",
107
+ "@dxos/plugin-graph": "0.13.0",
108
+ "@dxos/plugin-attention": "0.13.0",
109
+ "@dxos/plugin-settings": "0.13.0",
110
+ "@dxos/plugin-theme": "0.13.0",
111
+ "@dxos/react-ui-dnd": "0.13.0",
112
+ "@dxos/util": "0.13.0"
101
113
  },
102
114
  "devDependencies": {
103
115
  "@types/react": "~19.2.17",
104
116
  "@types/react-dom": "~19.2.3",
105
- "effect": "4.0.0-rc.108",
117
+ "effect": "4.0.0",
106
118
  "react": "~19.2.7",
107
119
  "react-dom": "~19.2.7",
108
- "vite": "^8.2.1",
109
- "@dxos/react-ui": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
110
- "@dxos/ui-theme": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264"
120
+ "vite": "^8.2.2",
121
+ "@dxos/react-ui": "0.13.0",
122
+ "@dxos/ui-theme": "0.13.0"
111
123
  },
112
124
  "peerDependencies": {
113
- "effect": "4.0.0-rc.108",
125
+ "effect": "4.0.0",
114
126
  "react": "~19.2.7",
115
127
  "react-dom": "~19.2.7",
116
- "@dxos/react-ui": "^0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
117
- "@dxos/ui-theme": "^0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264"
128
+ "@dxos/react-ui": "^0.13.0",
129
+ "@dxos/ui-theme": "^0.13.0"
118
130
  },
119
131
  "publishConfig": {
120
132
  "access": "public"
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ // @import-as-namespace
6
+
7
+ export { corePlugins as make } from './core.ts';
package/src/Corpus.ts ADDED
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ // @import-as-namespace
6
+
7
+ export * from './corpus/index.ts';
@@ -2,8 +2,10 @@
2
2
  // Copyright 2026 DXOS.org
3
3
  //
4
4
 
5
- import { ProcessManagerPlugin } from '@dxos/app-framework';
5
+ // @import-as-namespace
6
+
6
7
  import type * as Plugin from '@dxos/app-framework/Plugin';
8
+ import * as ProcessManagerPlugin from '@dxos/app-framework/ProcessManagerPlugin';
7
9
  import { type TestAppOptions, type TestHarness, createTestApp } from '@dxos/app-framework/testing';
8
10
  import { AttentionPlugin } from '@dxos/plugin-attention/testing';
9
11
  import { GraphPlugin } from '@dxos/plugin-graph/testing';
@@ -27,7 +29,7 @@ export type ComposerTestAppOptions = Omit<TestAppOptions, 'plugins'> & {
27
29
  const headlessCorePlugins = (): Plugin.Plugin[] => [
28
30
  AttentionPlugin.make(),
29
31
  GraphPlugin.make(),
30
- ProcessManagerPlugin(),
32
+ ProcessManagerPlugin.make(),
31
33
  SettingsPlugin.make(),
32
34
  ];
33
35
 
@@ -48,7 +50,7 @@ export const createComposerTestApp = async (opts: ComposerTestAppOptions = {}):
48
50
  const core = headlessCorePlugins();
49
51
  if (theme) {
50
52
  const { ThemePlugin } = await import('@dxos/plugin-theme/testing');
51
- const { defaultTx } = await import('@dxos/react-ui');
53
+ const { defaultTx } = await import('@dxos/react-ui/Theme');
52
54
  core.push(ThemePlugin.make({ tx: defaultTx }));
53
55
  }
54
56
  return createTestApp({
@@ -0,0 +1,23 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ // GENERATED by dx-plugin — do not edit. Sliced from ../index.ts for the 'browser' condition.
6
+
7
+ import * as ActivationEvents from '@dxos/app-framework/ActivationEvents';
8
+ import * as Capability from '@dxos/app-framework/Capability';
9
+ import * as AppCapabilities from '@dxos/app-toolkit/AppCapabilities';
10
+ import * as AppCapability from '@dxos/app-toolkit/AppCapability';
11
+
12
+ import { StorybookCapabilities } from '#types';
13
+
14
+ export const OperationHandler = AppCapability.operationHandler(() => import('../operation-handler.ts'));
15
+
16
+ export const ReactContext = AppCapability.reactContext(() => import('../react-context.tsx'));
17
+
18
+ export const State = Capability.lazyModule(
19
+ 'State',
20
+ // Shell state read by `Layout` on its first render — same class as the deck's `DeckState`.
21
+ { activatesOn: ActivationEvents.Startup, provides: [StorybookCapabilities.LayoutState, AppCapabilities.Layout] },
22
+ () => import('../state.tsx'),
23
+ );
@@ -11,13 +11,13 @@ import * as AppCapability from '@dxos/app-toolkit/AppCapability';
11
11
 
12
12
  import { StorybookCapabilities } from '#types';
13
13
 
14
- export const OperationHandler = AppCapability.operationHandler(() => import('../operation-handler'));
14
+ export const OperationHandler = AppCapability.operationHandler(() => import('../operation-handler.ts'));
15
15
 
16
16
  export const State = Capability.lazyModule(
17
17
  'State',
18
18
  // Shell state read by `Layout` on its first render — same class as the deck's `DeckState`.
19
19
  { activatesOn: ActivationEvents.Startup, provides: [StorybookCapabilities.LayoutState, AppCapabilities.Layout] },
20
- () => import('../state'),
20
+ () => import('../state.tsx'),
21
21
  );
22
22
 
23
23
  export const ReactContext = undefined;
@@ -0,0 +1,23 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ // GENERATED by dx-plugin — do not edit. Sliced from ../index.ts for the 'tauri' condition.
6
+
7
+ import * as ActivationEvents from '@dxos/app-framework/ActivationEvents';
8
+ import * as Capability from '@dxos/app-framework/Capability';
9
+ import * as AppCapabilities from '@dxos/app-toolkit/AppCapabilities';
10
+ import * as AppCapability from '@dxos/app-toolkit/AppCapability';
11
+
12
+ import { StorybookCapabilities } from '#types';
13
+
14
+ export const OperationHandler = AppCapability.operationHandler(() => import('../operation-handler.ts'));
15
+
16
+ export const ReactContext = AppCapability.reactContext(() => import('../react-context.tsx'));
17
+
18
+ export const State = Capability.lazyModule(
19
+ 'State',
20
+ // Shell state read by `Layout` on its first render — same class as the deck's `DeckState`.
21
+ { activatesOn: ActivationEvents.Startup, provides: [StorybookCapabilities.LayoutState, AppCapabilities.Layout] },
22
+ () => import('../state.tsx'),
23
+ );
@@ -11,13 +11,13 @@ import * as AppCapability from '@dxos/app-toolkit/AppCapability';
11
11
 
12
12
  import { StorybookCapabilities } from '#types';
13
13
 
14
- export const OperationHandler = AppCapability.operationHandler(() => import('../operation-handler'));
14
+ export const OperationHandler = AppCapability.operationHandler(() => import('../operation-handler.ts'));
15
15
 
16
16
  export const State = Capability.lazyModule(
17
17
  'State',
18
18
  // Shell state read by `Layout` on its first render — same class as the deck's `DeckState`.
19
19
  { activatesOn: ActivationEvents.Startup, provides: [StorybookCapabilities.LayoutState, AppCapabilities.Layout] },
20
- () => import('../state'),
20
+ () => import('../state.tsx'),
21
21
  );
22
22
 
23
23
  export const ReactContext = undefined;
@@ -9,11 +9,11 @@ import * as AppCapability from '@dxos/app-toolkit/AppCapability';
9
9
 
10
10
  import { StorybookCapabilities } from '#types';
11
11
 
12
- export const OperationHandler = AppCapability.operationHandler(() => import('./operation-handler'));
13
- export const ReactContext = AppCapability.reactContext(() => import('./react-context'));
12
+ export const OperationHandler = AppCapability.operationHandler(() => import('./operation-handler.ts'));
13
+ export const ReactContext = AppCapability.reactContext(() => import('./react-context.tsx'));
14
14
  export const State = Capability.lazyModule(
15
15
  'State',
16
16
  // Shell state read by `Layout` on its first render — same class as the deck's `DeckState`.
17
17
  { activatesOn: ActivationEvents.Startup, provides: [StorybookCapabilities.LayoutState, AppCapabilities.Layout] },
18
- () => import('./state'),
18
+ () => import('./state.tsx'),
19
19
  );
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import * as Effect from 'effect/Effect';
6
- import * as Atom from 'effect/unstable/reactivity/Atom';
6
+ import * as Atom from 'effect/reactivity/Atom';
7
7
 
8
8
  import * as Capability from '@dxos/app-framework/Capability';
9
9
  import * as AppCapabilities from '@dxos/app-toolkit/AppCapabilities';
@@ -4,24 +4,31 @@
4
4
 
5
5
  import { useAtomValue } from '@effect/atom-react/Hooks';
6
6
  import { RegistryContext } from '@effect/atom-react/RegistryContext';
7
- import React, { type PropsWithChildren, useCallback, useContext, useEffect, useRef, useState } from 'react';
8
-
9
- import { Surface, useCapability } from '@dxos/app-framework/ui';
7
+ import React, {
8
+ type ComponentProps,
9
+ type PropsWithChildren,
10
+ useCallback,
11
+ useContext,
12
+ useEffect,
13
+ useRef,
14
+ useState,
15
+ } from 'react';
16
+
17
+ import * as Hooks from '@dxos/app-framework/Hooks';
18
+ import * as Surface from '@dxos/app-framework/Surface';
19
+ import * as AppSurface from '@dxos/app-toolkit/AppSurface';
10
20
  import type * as LayoutOperation from '@dxos/app-toolkit/LayoutOperation';
11
- import { AppSurface } from '@dxos/app-toolkit/ui';
12
- import {
13
- AlertDialog,
14
- Button,
15
- Dialog,
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
21
  import { Dnd } from '@dxos/react-ui-dnd';
22
+ import * as AlertDialog from '@dxos/react-ui/AlertDialog';
23
+ import * as Card from '@dxos/react-ui/Card';
24
+ import * as Dialog from '@dxos/react-ui/Dialog';
25
+ import * as DragHandle from '@dxos/react-ui/DragHandle';
26
+ import * as UiHooks from '@dxos/react-ui/Hooks';
27
+ import * as Main from '@dxos/react-ui/Main';
28
+ import * as Popover from '@dxos/react-ui/Popover';
29
+ import * as Theme from '@dxos/react-ui/Theme';
30
+ import * as Toast from '@dxos/react-ui/Toast';
31
+ import * as VirtualAnchor from '@dxos/react-ui/VirtualAnchor';
25
32
  import { descriptionMessage, mx } from '@dxos/ui-theme';
26
33
 
27
34
  import { meta } from '#meta';
@@ -29,8 +36,10 @@ import { StorybookCapabilities } from '#types';
29
36
 
30
37
  const debounce_delay = 100;
31
38
 
39
+ type FocusOutsideEvent = Parameters<NonNullable<ComponentProps<typeof Popover.Root>['onFocusOutside']>>[0];
40
+
32
41
  const StoryToast = ({ toast, onDismiss }: { toast: LayoutOperation.Toast; onDismiss: (id: string) => void }) => {
33
- const { t } = useTranslation(meta.profile.key);
42
+ const { t } = UiHooks.useTranslation(meta.profile.key);
34
43
  return (
35
44
  <Toast.Root
36
45
  data-testid={toast.id}
@@ -42,28 +51,26 @@ const StoryToast = ({ toast, onDismiss }: { toast: LayoutOperation.Toast; onDism
42
51
  }
43
52
  }}
44
53
  >
45
- <Toast.Title icon={toast.icon} onClose={toast.closeLabel ? () => onDismiss(toast.id) : undefined}>
46
- {toast.title && <span>{toLocalizedString(toast.title, t)}</span>}
47
- </Toast.Title>
48
- {toast.description && <Toast.Description>{toLocalizedString(toast.description, t)}</Toast.Description>}
54
+ <Toast.Header icon={toast.icon} closable={!!toast.closeLabel}>
55
+ {toast.title && Theme.toLocalizedString(toast.title, t)}
56
+ </Toast.Header>
57
+ {toast.description && <Toast.Description>{Theme.toLocalizedString(toast.description, t)}</Toast.Description>}
49
58
  {toast.onAction && toast.actionAlt && toast.actionLabel && (
50
- <Toast.Actions>
51
- <Toast.Action altText={toLocalizedString(toast.actionAlt, t)} asChild>
52
- <Button variant='primary' onClick={() => toast.onAction?.()}>
53
- {toLocalizedString(toast.actionLabel, t)}
54
- </Button>
55
- </Toast.Action>
56
- </Toast.Actions>
59
+ <Toast.Footer>
60
+ <Toast.ActionTrigger variant='primary' onClick={() => toast.onAction?.()}>
61
+ {Theme.toLocalizedString(toast.actionLabel, t)}
62
+ </Toast.ActionTrigger>
63
+ </Toast.Footer>
57
64
  )}
58
65
  </Toast.Root>
59
66
  );
60
67
  };
61
68
 
62
69
  export const Layout = ({ children }: PropsWithChildren<{}>) => {
63
- const { t } = useTranslation(meta.profile.key);
70
+ const { t } = UiHooks.useTranslation(meta.profile.key);
64
71
  const trigger = useRef<HTMLButtonElement | null>(null);
65
72
  const registry = useContext(RegistryContext);
66
- const stateAtom = useCapability(StorybookCapabilities.LayoutState);
73
+ const stateAtom = Hooks.useCapability(StorybookCapabilities.LayoutState);
67
74
  const layout = useAtomValue(stateAtom);
68
75
  const [iter, setIter] = useState(0);
69
76
  const [open, setOpen] = useState(false);
@@ -100,13 +107,11 @@ export const Layout = ({ children }: PropsWithChildren<{}>) => {
100
107
  });
101
108
  }, [updateState]);
102
109
 
103
- const handleInteractOutside = useCallback(
104
- (event: KeyboardEvent | PopoverContentInteractOutsideEvent) => {
105
- if (
106
- // TODO(thure): CodeMirror should not focus itself when it updates.
107
- event.type === 'dismissableLayer.focusOutside' &&
108
- (event.currentTarget as HTMLElement | undefined)?.classList.contains('cm-content')
109
- ) {
110
+ const handleFocusOutside = useCallback(
111
+ (event: FocusOutsideEvent) => {
112
+ // TODO(thure): CodeMirror should not focus itself when it updates.
113
+ const target = event.detail.originalEvent.target;
114
+ if (target instanceof HTMLElement && target.classList.contains('cm-content')) {
110
115
  event.preventDefault();
111
116
  } else {
112
117
  handleClose();
@@ -123,13 +128,27 @@ export const Layout = ({ children }: PropsWithChildren<{}>) => {
123
128
  );
124
129
 
125
130
  const DialogRoot = layout.dialogType === 'alert' ? AlertDialog.Root : Dialog.Root;
126
- const DialogOverlay = layout.dialogType === 'alert' ? AlertDialog.Overlay : Dialog.Overlay;
127
131
 
128
132
  return (
129
133
  <Toast.Provider>
134
+ {/* The plugin `ReactContext` capabilities — the theme plugin's `Tooltip.Provider` among them —
135
+ wrap `children` only, so the dialog and popover portals rendered as its siblings below sit
136
+ outside them. Any `IconButton` there renders a `Tooltip.Trigger`, which throws rather than
137
+ degrades when it finds no provider, taking the whole surface down with it. */}
130
138
  <div className='fixed inset-0 flex overflow-hidden'>
131
139
  <Dnd.Root>
132
- <Popover.Root open={open}>
140
+ <Popover.Root
141
+ open={open}
142
+ positioning={{
143
+ ...VirtualAnchor.virtualAnchor(trigger),
144
+ placement: layout.popoverSide,
145
+ hideWhenDetached: true,
146
+ }}
147
+ autoFocus={false}
148
+ onFocusOutside={handleFocusOutside}
149
+ onPointerDownOutside={handleClose}
150
+ onEscapeKeyDown={handleClose}
151
+ >
133
152
  <Main.Root
134
153
  navigationSidebarState={layout.sidebarState}
135
154
  complementarySidebarState={layout.complementarySidebarState}
@@ -142,78 +161,64 @@ export const Layout = ({ children }: PropsWithChildren<{}>) => {
142
161
  <DialogRoot
143
162
  modal={layout.dialogBlockAlign !== 'end'}
144
163
  open={layout.dialogOpen}
145
- onOpenChange={(nextOpen) => updateState({ dialogOpen: nextOpen })}
164
+ onOpenChange={({ open: nextOpen }) => updateState({ dialogOpen: nextOpen })}
146
165
  >
147
- {layout.dialogBlockAlign === 'end' ? (
148
- <Surface.Surface
149
- type={AppSurface.Dialog}
150
- data={layout.dialogContent}
151
- limit={1}
152
- fallback={ErrorFallback}
153
- placeholder={<div />}
154
- />
155
- ) : (
156
- <DialogOverlay
157
- blockAlign={layout.dialogBlockAlign}
158
- classNames={layout.dialogOverlayClasses}
159
- style={layout.dialogOverlayStyle}
160
- >
161
- <Surface.Surface
162
- type={AppSurface.Dialog}
163
- data={layout.dialogContent}
164
- limit={1}
165
- fallback={ErrorFallback}
166
- />
167
- </DialogOverlay>
168
- )}
166
+ {/* The dialog surface renders its own Content, which carries the placement and scrim. */}
167
+ <Surface.Surface
168
+ type={AppSurface.Dialog}
169
+ data={layout.dialogContent}
170
+ limit={1}
171
+ fallback={ErrorFallback}
172
+ placeholder={<div />}
173
+ />
169
174
  </DialogRoot>
170
175
 
171
- <Popover.VirtualTrigger key={iter} virtualRef={trigger} />
172
- <Popover.Portal>
173
- <Popover.Content
174
- side={layout.popoverSide}
175
- onOpenAutoFocus={(event) => event.preventDefault()}
176
- onInteractOutside={handleInteractOutside}
177
- onEscapeKeyDown={handleInteractOutside}
178
- sticky='always'
179
- hideWhenDetached
180
- >
181
- <Popover.Viewport>
182
- {layout.popoverKind === 'card' && (
183
- <Card.Root>
184
- <Card.Header>
185
- {/* Disabled drag handle keeps the toolbar slot layout consistent with regular cards. */}
186
- <Card.DragHandle />
187
- {layout.popoverTitle ? (
188
- <Card.Title>{toLocalizedString(layout.popoverTitle, t)}</Card.Title>
189
- ) : (
190
- <span />
191
- )}
192
- <Card.ActionIconButton action='close' onClick={handleClose} />
193
- </Card.Header>
176
+ <Popover.Content>
177
+ <Popover.Body>
178
+ {/* `border={false}`: the popover content already draws the surface and its border,
179
+ so a bordered card inside it reads as a second frame. Matches the deck's popover. */}
180
+ {layout.popoverKind === 'card' && (
181
+ <Card.Root grid border={false} classNames='dx-card-popover rounded-md'>
182
+ <Card.Header>
183
+ {/* Disabled drag handle keeps the toolbar slot layout consistent with regular cards. */}
184
+ <DragHandle.DragHandle />
185
+ {layout.popoverTitle ? (
186
+ <Card.Title>{Theme.toLocalizedString(layout.popoverTitle, t)}</Card.Title>
187
+ ) : (
188
+ <span />
189
+ )}
190
+ <Card.Action system='close' onClick={handleClose} />
191
+ </Card.Header>
192
+ {layout.popoverContent ? (
194
193
  <Surface.Surface type={AppSurface.CardContent} data={layout.popoverContent} limit={1} />
195
- </Card.Root>
196
- )}
197
- {(layout.popoverKind === 'base' || layout.popoverKind === 'rename') && (
198
- <Surface.Surface type={AppSurface.Popover} data={layout.popoverContent} limit={1} />
199
- )}
200
- </Popover.Viewport>
201
- <Popover.Arrow />
202
- </Popover.Content>
203
- </Popover.Portal>
194
+ ) : (
195
+ // Matches the deck's popover, which opens a card with no subject for a link that did not resolve.
196
+ <Card.Body classNames='min-h-8'>
197
+ <Card.Row>
198
+ <Card.Text variant='muted'>No preview available.</Card.Text>
199
+ </Card.Row>
200
+ </Card.Body>
201
+ )}
202
+ </Card.Root>
203
+ )}
204
+ {(layout.popoverKind === 'base' || layout.popoverKind === 'rename') && (
205
+ <Surface.Surface type={AppSurface.Popover} data={layout.popoverContent} limit={1} />
206
+ )}
207
+ </Popover.Body>
208
+ </Popover.Content>
204
209
  </Popover.Root>
205
210
  </Dnd.Root>
206
211
  {layout.toasts.map((toast) => (
207
212
  <StoryToast key={toast.id} toast={toast} onDismiss={handleDismissToast} />
208
213
  ))}
209
- <Toast.Viewport />
214
+ <Toast.Toaster />
210
215
  </div>
211
216
  </Toast.Provider>
212
217
  );
213
218
  };
214
219
 
215
220
  export const ErrorFallback = ({ error }: { error?: Error }) => {
216
- const { t } = useTranslation(meta.profile.key);
221
+ const { t } = UiHooks.useTranslation(meta.profile.key);
217
222
  const errorString = error?.toString() ?? '';
218
223
  return (
219
224
  <div
@@ -2,4 +2,4 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- export * from './Layout';
5
+ export * from './Layout.tsx';
@@ -2,4 +2,4 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- export * from './Layout';
5
+ export * from './Layout/index.ts';
package/src/core.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { ProcessManagerPlugin } from '@dxos/app-framework';
6
5
  import type * as Plugin from '@dxos/app-framework/Plugin';
6
+ import * as ProcessManagerPlugin from '@dxos/app-framework/ProcessManagerPlugin';
7
7
  // `/testing` entrypoints re-export the plugin eagerly (without `Plugin.lazy`).
8
8
  // The default `.` and `/plugin` exports wrap each plugin in a lazy stub
9
9
  // (`() => import('./XPlugin')`), which webkit cannot reliably resolve under
@@ -21,7 +21,7 @@ import { AttentionPlugin } from '@dxos/plugin-attention/testing';
21
21
  import { GraphPlugin } from '@dxos/plugin-graph/testing';
22
22
  import { SettingsPlugin } from '@dxos/plugin-settings/testing';
23
23
  import { ThemePlugin } from '@dxos/plugin-theme/testing';
24
- import { defaultTx } from '@dxos/react-ui';
24
+ import * as Theme from '@dxos/react-ui/Theme';
25
25
 
26
26
  /**
27
27
  * Core plugins for testing/storybook environments.
@@ -31,7 +31,7 @@ import { defaultTx } from '@dxos/react-ui';
31
31
  export const corePlugins = (): Plugin.Plugin[] => [
32
32
  AttentionPlugin.make(),
33
33
  GraphPlugin.make(),
34
- ProcessManagerPlugin(),
34
+ ProcessManagerPlugin.make(),
35
35
  SettingsPlugin.make(),
36
- ThemePlugin.make({ tx: defaultTx }),
36
+ ThemePlugin.make({ tx: Theme.defaultTx }),
37
37
  ];
@@ -2,4 +2,4 @@
2
2
  // Copyright 2026 DXOS.org
3
3
  //
4
4
 
5
- export * from './sample-messages';
5
+ export * from './sample-messages.ts';
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { describe, expect, test } from 'vitest';
6
6
 
7
- import { SAMPLE_MESSAGES, type SampleMessage } from './sample-messages';
7
+ import { SAMPLE_MESSAGES, type SampleMessage } from './sample-messages.ts';
8
8
 
9
9
  const TOPICS: SampleMessage['topic'][] = ['project', 'finance', 'scheduling', 'hiring', 'ops'];
10
10
 
package/src/index.ts CHANGED
@@ -7,8 +7,8 @@
7
7
  // Storybook on webkit + vite-dev cannot reliably resolve the lazy form's
8
8
  // dynamic `import()` — see `./core.ts` for the underlying chunk-init issue
9
9
  // — and there is no production code-splitting benefit to recover here.
10
- export * as StorybookPlugin from './StorybookPlugin';
11
- export * from './core';
12
- export * from './corpus';
13
- export * from '#meta';
10
+ export * as StorybookPlugin from './StorybookPlugin.ts';
14
11
  export * from '#types';
12
+ export * as CorePlugins from './CorePlugins.ts';
13
+ export * as Corpus from './Corpus.ts';
14
+ export * as Harness from './Harness.ts';
package/src/meta.ts CHANGED
@@ -4,6 +4,6 @@
4
4
 
5
5
  import * as Plugin from '@dxos/app-framework/Plugin';
6
6
 
7
- import config from '../dx.config';
7
+ import config from '../dx.config.ts';
8
8
 
9
9
  export const meta = Plugin.getMetaFromConfig(config);
@@ -7,7 +7,7 @@ import * as Effect from 'effect/Effect';
7
7
  import * as LayoutOperation from '@dxos/app-toolkit/LayoutOperation';
8
8
  import * as Operation from '@dxos/compute/Operation';
9
9
 
10
- import { updateState } from './update-state';
10
+ import { updateState } from './update-state.ts';
11
11
 
12
12
  const handler: Operation.WithHandler<typeof LayoutOperation.AddToast> = LayoutOperation.AddToast.pipe(
13
13
  Operation.withHandler(