@dxos/plugin-devtools 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 (144) hide show
  1. package/PLUGIN.mdl +144 -57
  2. package/dist/lib/Devtools.mjs +9 -1
  3. package/dist/lib/Devtools.mjs.map +1 -1
  4. package/dist/lib/capabilities.browser.mjs +45 -0
  5. package/dist/lib/capabilities.browser.mjs.map +1 -0
  6. package/dist/lib/capabilities.mjs +9 -7
  7. package/dist/lib/capabilities.mjs.map +1 -1
  8. package/dist/lib/capabilities.node.mjs +7 -3
  9. package/dist/lib/capabilities.node.mjs.map +1 -1
  10. package/dist/lib/capabilities.tauri.mjs +45 -0
  11. package/dist/lib/capabilities.tauri.mjs.map +1 -0
  12. package/dist/lib/capabilities.workerd.mjs +2 -2
  13. package/dist/lib/capabilities.workerd.mjs.map +1 -1
  14. package/dist/lib/{chunk-CliPanel.mjs → chunk-CliArticle.mjs} +28 -24
  15. package/dist/lib/chunk-CliArticle.mjs.map +1 -0
  16. package/dist/lib/chunk-DebugGraph.mjs +12 -8
  17. package/dist/lib/chunk-DebugGraph.mjs.map +1 -1
  18. package/dist/lib/chunk-DevtoolsCards.mjs +108 -0
  19. package/dist/lib/chunk-DevtoolsCards.mjs.map +1 -0
  20. package/dist/lib/chunk-DevtoolsOverviewContainer.mjs +59 -22
  21. package/dist/lib/chunk-DevtoolsOverviewContainer.mjs.map +1 -1
  22. package/dist/lib/chunk-GithubPanel.mjs +13 -10
  23. package/dist/lib/chunk-GithubPanel.mjs.map +1 -1
  24. package/dist/lib/chunk-PLUGIN.mjs +6 -0
  25. package/dist/lib/chunk-PLUGIN.mjs.map +1 -0
  26. package/dist/lib/{chunk-RegistryPanel.mjs → chunk-RegistryArticle.mjs} +26 -25
  27. package/dist/lib/chunk-RegistryArticle.mjs.map +1 -0
  28. package/dist/lib/chunk-ToolsExplorerContainer.mjs +7 -3
  29. package/dist/lib/chunk-ToolsExplorerContainer.mjs.map +1 -1
  30. package/dist/lib/chunk-app-graph-builder.mjs +323 -312
  31. package/dist/lib/chunk-app-graph-builder.mjs.map +1 -1
  32. package/dist/lib/chunk-react-context.mjs +1 -1
  33. package/dist/lib/chunk-react-context.mjs.map +1 -1
  34. package/dist/lib/chunk-react-surface.mjs +244 -88
  35. package/dist/lib/chunk-react-surface.mjs.map +1 -1
  36. package/dist/lib/containers.mjs +3 -3
  37. package/dist/lib/containers.mjs.map +1 -1
  38. package/dist/lib/index.mjs +0 -1
  39. package/dist/lib/meta.mjs +2 -2
  40. package/dist/lib/meta.mjs.map +1 -1
  41. package/dist/lib/translations.mjs +55 -49
  42. package/dist/lib/translations.mjs.map +1 -1
  43. package/dist/types/src/capabilities/DevtoolsCards.d.ts +28 -0
  44. package/dist/types/src/capabilities/DevtoolsCards.d.ts.map +1 -0
  45. package/dist/types/src/capabilities/DevtoolsSurfaces.d.ts +19 -6
  46. package/dist/types/src/capabilities/DevtoolsSurfaces.d.ts.map +1 -1
  47. package/dist/types/src/capabilities/app-graph-builder.d.ts +19 -0
  48. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  49. package/dist/types/src/capabilities/app-graph-builder.test.d.ts +2 -0
  50. package/dist/types/src/capabilities/app-graph-builder.test.d.ts.map +1 -0
  51. package/dist/types/src/capabilities/gen/browser.d.ts +8 -0
  52. package/dist/types/src/capabilities/gen/browser.d.ts.map +1 -0
  53. package/dist/types/src/capabilities/gen/tauri.d.ts +8 -0
  54. package/dist/types/src/capabilities/gen/tauri.d.ts.map +1 -0
  55. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  56. package/dist/types/src/containers/CliArticle/CliArticle.d.ts +8 -0
  57. package/dist/types/src/containers/CliArticle/CliArticle.d.ts.map +1 -0
  58. package/dist/types/src/containers/{CliPanel/CliPanel.stories.d.ts → CliArticle/CliArticle.stories.d.ts} +33 -5
  59. package/dist/types/src/containers/CliArticle/CliArticle.stories.d.ts.map +1 -0
  60. package/dist/types/src/containers/CliArticle/index.d.ts +2 -0
  61. package/dist/types/src/containers/CliArticle/index.d.ts.map +1 -0
  62. package/dist/types/src/containers/{CliPanel → CliArticle}/useCliApp.d.ts +1 -1
  63. package/dist/types/src/containers/CliArticle/useCliApp.d.ts.map +1 -0
  64. package/dist/types/src/containers/DebugGraph/DebugGraph.d.ts +2 -1
  65. package/dist/types/src/containers/DebugGraph/DebugGraph.d.ts.map +1 -1
  66. package/dist/types/src/containers/DebugGraph/index.d.ts +1 -1
  67. package/dist/types/src/containers/DebugGraph/index.d.ts.map +1 -1
  68. package/dist/types/src/containers/DevtoolsOverviewContainer/DevtoolsOverviewContainer.d.ts.map +1 -1
  69. package/dist/types/src/containers/DevtoolsOverviewContainer/index.d.ts +1 -1
  70. package/dist/types/src/containers/DevtoolsOverviewContainer/index.d.ts.map +1 -1
  71. package/dist/types/src/containers/GithubPanel/GithubComponent.d.ts.map +1 -1
  72. package/dist/types/src/containers/GithubPanel/GithubComponent.stories.d.ts +30 -2
  73. package/dist/types/src/containers/GithubPanel/GithubComponent.stories.d.ts.map +1 -1
  74. package/dist/types/src/containers/GithubPanel/index.d.ts +1 -1
  75. package/dist/types/src/containers/GithubPanel/index.d.ts.map +1 -1
  76. package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts +8 -0
  77. package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts.map +1 -0
  78. package/dist/types/src/containers/RegistryArticle/index.d.ts +2 -0
  79. package/dist/types/src/containers/RegistryArticle/index.d.ts.map +1 -0
  80. package/dist/types/src/containers/ToolsExplorerContainer/ToolsExplorerContainer.d.ts +3 -1
  81. package/dist/types/src/containers/ToolsExplorerContainer/ToolsExplorerContainer.d.ts.map +1 -1
  82. package/dist/types/src/containers/ToolsExplorerContainer/index.d.ts +1 -1
  83. package/dist/types/src/containers/ToolsExplorerContainer/index.d.ts.map +1 -1
  84. package/dist/types/src/containers/index.d.ts +2 -2
  85. package/dist/types/src/containers/index.d.ts.map +1 -1
  86. package/dist/types/src/index.d.ts +1 -2
  87. package/dist/types/src/index.d.ts.map +1 -1
  88. package/dist/types/src/translations.d.ts +30 -2
  89. package/dist/types/src/translations.d.ts.map +1 -1
  90. package/dist/types/src/types/Devtools.d.ts +6 -0
  91. package/dist/types/src/types/Devtools.d.ts.map +1 -1
  92. package/dist/types/src/types/index.d.ts +2 -2
  93. package/dist/types/src/types/index.d.ts.map +1 -1
  94. package/dist/types/tsconfig.tsbuildinfo +1 -1
  95. package/dx.config.ts +2 -2
  96. package/package.json +43 -39
  97. package/src/capabilities/DevtoolsCards.tsx +140 -0
  98. package/src/capabilities/DevtoolsSurfaces.tsx +32 -38
  99. package/src/capabilities/app-graph-builder.test.ts +75 -0
  100. package/src/capabilities/app-graph-builder.ts +324 -313
  101. package/src/capabilities/gen/browser.ts +61 -0
  102. package/src/capabilities/gen/node.ts +4 -4
  103. package/src/capabilities/gen/tauri.ts +61 -0
  104. package/src/capabilities/gen/workerd.ts +2 -2
  105. package/src/capabilities/index.ts +7 -7
  106. package/src/capabilities/react-context.tsx +1 -1
  107. package/src/capabilities/react-surface.ts +222 -79
  108. package/src/containers/{CliPanel/CliPanel.stories.tsx → CliArticle/CliArticle.stories.tsx} +9 -10
  109. package/src/containers/{CliPanel/CliPanel.tsx → CliArticle/CliArticle.tsx} +7 -7
  110. package/src/containers/CliArticle/index.ts +5 -0
  111. package/src/containers/{CliPanel → CliArticle}/useCliApp.ts +17 -12
  112. package/src/containers/DebugGraph/DebugGraph.tsx +7 -6
  113. package/src/containers/DebugGraph/index.ts +1 -1
  114. package/src/containers/DevtoolsOverviewContainer/DevtoolsOverviewContainer.tsx +72 -32
  115. package/src/containers/DevtoolsOverviewContainer/index.ts +1 -1
  116. package/src/containers/GithubPanel/GithubComponent.stories.tsx +2 -2
  117. package/src/containers/GithubPanel/GithubComponent.tsx +14 -11
  118. package/src/containers/GithubPanel/GithubPanel.tsx +2 -2
  119. package/src/containers/GithubPanel/index.ts +1 -1
  120. package/src/containers/{RegistryPanel/RegistryPanel.tsx → RegistryArticle/RegistryArticle.tsx} +11 -10
  121. package/src/containers/RegistryArticle/index.ts +5 -0
  122. package/src/containers/ToolsExplorerContainer/ToolsExplorerContainer.tsx +10 -3
  123. package/src/containers/ToolsExplorerContainer/index.ts +1 -1
  124. package/src/containers/index.ts +6 -6
  125. package/src/index.ts +1 -2
  126. package/src/meta.ts +1 -1
  127. package/src/plugin.test.ts +2 -2
  128. package/src/translations.ts +7 -3
  129. package/src/types/Devtools.ts +10 -0
  130. package/src/types/index.ts +2 -2
  131. package/dist/lib/chunk-CliPanel.mjs.map +0 -1
  132. package/dist/lib/chunk-RegistryPanel.mjs.map +0 -1
  133. package/dist/types/src/containers/CliPanel/CliPanel.d.ts +0 -6
  134. package/dist/types/src/containers/CliPanel/CliPanel.d.ts.map +0 -1
  135. package/dist/types/src/containers/CliPanel/CliPanel.stories.d.ts.map +0 -1
  136. package/dist/types/src/containers/CliPanel/index.d.ts +0 -2
  137. package/dist/types/src/containers/CliPanel/index.d.ts.map +0 -1
  138. package/dist/types/src/containers/CliPanel/useCliApp.d.ts.map +0 -1
  139. package/dist/types/src/containers/RegistryPanel/RegistryPanel.d.ts +0 -6
  140. package/dist/types/src/containers/RegistryPanel/RegistryPanel.d.ts.map +0 -1
  141. package/dist/types/src/containers/RegistryPanel/index.d.ts +0 -2
  142. package/dist/types/src/containers/RegistryPanel/index.d.ts.map +0 -1
  143. package/src/containers/CliPanel/index.ts +0 -5
  144. package/src/containers/RegistryPanel/index.ts +0 -5
package/dx.config.ts CHANGED
@@ -11,8 +11,8 @@ export default Config2.make({
11
11
  name: 'Devtools',
12
12
  author: 'DXOS',
13
13
  description: trim`
14
- DevtoolsPlugin is the developer inspector for DXOS Composer. It adds a structured Devtools node
15
- to the navigation graph — grouped into Client, HALO, ECHO, Mesh, and EDGE sub-sections —
14
+ DevtoolsPlugin is the developer inspector for DXOS Composer. It adds a structured DevTools tree
15
+ to the floating debug panel — grouped into Client, HALO, ECHO, Mesh, and EDGE sub-sections —
16
16
  exposing panel views for config, storage, logs, diagnostics, identity, devices, feeds,
17
17
  objects, schemas, automerge internals, network topology, EDGE workflows, and invocation
18
18
  traces, all driven by the @dxos/devtools component library.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-devtools",
3
- "version": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
3
+ "version": "0.13.0",
4
4
  "description": "DXOS developer inspector plugin.",
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
  "#containers": {
28
30
  "source": "./src/containers/index.ts",
@@ -98,51 +100,53 @@
98
100
  ],
99
101
  "dependencies": {
100
102
  "@bufbuild/protobuf": "2.11.0",
101
- "@dxos/app-framework": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
102
- "@dxos/app-graph": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
103
- "@dxos/app-toolkit": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
104
- "@dxos/async": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
105
- "@dxos/cli-util": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
106
- "@dxos/client": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
107
- "@dxos/client-services": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
108
- "@dxos/compute": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
109
- "@dxos/config": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
110
- "@dxos/devtools": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
111
- "@dxos/echo": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
112
- "@dxos/effect": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
113
- "@dxos/graph": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
114
- "@dxos/keys": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
115
- "@dxos/log": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
116
- "@dxos/plugin-script": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
117
- "@dxos/plugin-space": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
118
- "@dxos/protocols": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
119
- "@dxos/react-client": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
120
- "@dxos/react-ui-introspect": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
121
- "@dxos/react-ui-terminal": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
122
- "@dxos/util": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
123
- "@dxos/react-ui-table": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264"
103
+ "@dxos/app-framework": "0.13.0",
104
+ "@dxos/app-graph": "0.13.0",
105
+ "@dxos/app-toolkit": "0.13.0",
106
+ "@dxos/async": "0.13.0",
107
+ "@dxos/cli-util": "0.13.0",
108
+ "@dxos/client": "0.13.0",
109
+ "@dxos/compute": "0.13.0",
110
+ "@dxos/client-services": "0.13.0",
111
+ "@dxos/devtools": "0.13.0",
112
+ "@dxos/echo": "0.13.0",
113
+ "@dxos/config": "0.13.0",
114
+ "@dxos/echo-react": "0.13.0",
115
+ "@dxos/effect": "0.13.0",
116
+ "@dxos/graph": "0.13.0",
117
+ "@dxos/log": "0.13.0",
118
+ "@dxos/plugin-debug": "0.13.0",
119
+ "@dxos/keys": "0.13.0",
120
+ "@dxos/plugin-script": "0.13.0",
121
+ "@dxos/protocols": "0.13.0",
122
+ "@dxos/react-client": "0.13.0",
123
+ "@dxos/react-ui-table": "0.13.0",
124
+ "@dxos/react-ui-terminal": "0.13.0",
125
+ "@dxos/react-ui-introspect": "0.13.0",
126
+ "@dxos/plugin-space": "0.13.0",
127
+ "@dxos/util": "0.13.0"
124
128
  },
125
129
  "devDependencies": {
126
- "@effect/atom-react": "4.0.0-rc.108",
127
- "@storybook/react-vite": "^10.5.8",
130
+ "@effect/atom-react": "4.0.0",
131
+ "@storybook/react-vite": "^10.6.0",
128
132
  "@types/react": "~19.2.17",
129
133
  "@types/react-dom": "~19.2.3",
130
- "effect": "4.0.0-rc.108",
134
+ "effect": "4.0.0",
131
135
  "react": "~19.2.7",
132
136
  "react-dom": "~19.2.7",
133
- "vite": "^8.2.1",
134
- "@dxos/plugin-client": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
135
- "@dxos/ui-theme": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
136
- "@dxos/plugin-testing": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
137
- "@dxos/react-ui": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264"
137
+ "vite": "^8.2.2",
138
+ "@dxos/plugin-client": "0.13.0",
139
+ "@dxos/react-ui": "0.13.0",
140
+ "@dxos/plugin-testing": "0.13.0",
141
+ "@dxos/ui-theme": "0.13.0"
138
142
  },
139
143
  "peerDependencies": {
140
- "@effect/atom-react": "4.0.0-rc.108",
141
- "effect": "4.0.0-rc.108",
144
+ "@effect/atom-react": "4.0.0",
145
+ "effect": "4.0.0",
142
146
  "react": "~19.2.7",
143
147
  "react-dom": "~19.2.7",
144
- "@dxos/ui-theme": "^0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
145
- "@dxos/react-ui": "^0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264"
148
+ "@dxos/react-ui": "^0.13.0",
149
+ "@dxos/ui-theme": "^0.13.0"
146
150
  },
147
151
  "publishConfig": {
148
152
  "access": "public"
@@ -0,0 +1,140 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ // Card surfaces whose data comes from a hook rather than the stack's `DevtoolsCardData`.
6
+
7
+ import React, { useCallback, useMemo, useState } from 'react';
8
+
9
+ import * as Hooks from '@dxos/app-framework/Hooks';
10
+ import * as Surface from '@dxos/app-framework/Surface';
11
+ import * as AppSurface from '@dxos/app-toolkit/AppSurface';
12
+ import {
13
+ EdgeCard,
14
+ IndexerCard,
15
+ QueriesCard,
16
+ type Stats,
17
+ SurfaceProfilerCard,
18
+ type SurfaceProfilerStats,
19
+ SwarmTraceCard,
20
+ SyncCard,
21
+ useEdgeStatus,
22
+ useIndexerRows,
23
+ useQueryMetrics,
24
+ useSwarmTrace,
25
+ useSyncRows,
26
+ } from '@dxos/devtools';
27
+ import * as DebugOperation from '@dxos/plugin-debug/DebugOperation';
28
+ import { SpaceState, useSpaces } from '@dxos/react-client/echo';
29
+
30
+ import { Devtools } from '#types';
31
+
32
+ /** Surfaces hosting the debug tooling; they and everything rendered inside them are not listed. */
33
+ const DEBUG_HOSTS = new Set(['devtoolsOverview', 'debugDrawer']);
34
+
35
+ /** Whether a mounted surface is part of the debug tooling rather than the app being inspected. */
36
+ export const isDebugSurface = ({ id, role, ancestors }: Surface.Mounted): boolean =>
37
+ role.endsWith('.devtoolsOverview') ||
38
+ (id !== undefined && DEBUG_HOSTS.has(id)) ||
39
+ ancestors.some((ancestor) => DEBUG_HOSTS.has(ancestor));
40
+
41
+ /** Data the stats stack passes to every `AppSurface.DevtoolsOverview` card. */
42
+ export type DevtoolsCardData = {
43
+ stats: Stats;
44
+ surfaceProfilerStats: SurfaceProfilerStats[];
45
+ onClearSurfaceProfiler?: () => void;
46
+ };
47
+
48
+ export const isDevtoolsCardData = (data: unknown): data is DevtoolsCardData =>
49
+ typeof data === 'object' && data !== null && 'stats' in data && 'surfaceProfilerStats' in data;
50
+
51
+ /**
52
+ * The card role narrowed to the stack's data. `Surface.makeFilter` types its data by the token, so
53
+ * the binding is built directly, as `AppSurface.subject` does.
54
+ */
55
+ export const devtoolsCard: Surface.Filter<DevtoolsCardData> = {
56
+ bindings: [{ role: AppSurface.DevtoolsOverview.role, guard: isDevtoolsCardData }],
57
+ };
58
+
59
+ export type EdgeCardSurfaceProps = Pick<DevtoolsCardData, 'stats'>;
60
+
61
+ export const EdgeCardSurface = ({ stats }: EdgeCardSurfaceProps) => {
62
+ const { status, refresh, copy } = useEdgeStatus();
63
+ const spaces = useSpaces({ all: true });
64
+ const spaceNames = useMemo(() => {
65
+ const names: Record<string, string> = {};
66
+ for (const space of spaces) {
67
+ const name = space.state.get() === SpaceState.SPACE_READY ? space.properties.name : undefined;
68
+ if (typeof name === 'string') {
69
+ names[space.id] = name;
70
+ }
71
+ }
72
+ return names;
73
+ }, [spaces]);
74
+ return <EdgeCard edge={stats.edge} status={status} spaceNames={spaceNames} onRefresh={refresh} onCopy={copy} />;
75
+ };
76
+
77
+ export type SurfaceProfilerCardSurfaceProps = Pick<DevtoolsCardData, 'surfaceProfilerStats' | 'onClearSurfaceProfiler'>;
78
+
79
+ /** The profiler card with the surface highlight overlay's flag, which lives on `window`, mirrored in state. */
80
+ export const SurfaceProfilerCardSurface = ({
81
+ surfaceProfilerStats,
82
+ onClearSurfaceProfiler,
83
+ }: SurfaceProfilerCardSurfaceProps) => {
84
+ const [debug, setDebug] = useState(() => Surface.isDebugEnabled());
85
+ const handleDebugChange = useCallback((enabled: boolean) => {
86
+ Surface.setDebug(enabled);
87
+ setDebug(enabled);
88
+ }, []);
89
+
90
+ // The selected role's surfaces with their data and dispatch metrics, following mounts and unmounts.
91
+ const selected = Surface.useSelected();
92
+ const mounted = Surface.useMounted();
93
+ const detail = useMemo(() => {
94
+ if (!selected) {
95
+ return undefined;
96
+ }
97
+ const metrics = new Map(Surface.getMetrics().map((metric) => [metric.id, metric]));
98
+ return mounted
99
+ .filter((surface) => surface.role === selected && !isDebugSurface(surface))
100
+ .map(({ id, role, data }) => ({ id, data, metric: metrics.get(`surface/${id}/${role}`) }));
101
+ }, [selected, mounted]);
102
+
103
+ return (
104
+ <SurfaceProfilerCard
105
+ stats={surfaceProfilerStats}
106
+ debug={debug}
107
+ onDebugChange={handleDebugChange}
108
+ onClear={onClearSurfaceProfiler}
109
+ selected={selected}
110
+ onSelect={Surface.select}
111
+ detail={detail}
112
+ />
113
+ );
114
+ };
115
+
116
+ export const SwarmTraceCardSurface = () => {
117
+ const { messages, spaceCount, available, clear } = useSwarmTrace();
118
+ return <SwarmTraceCard messages={messages} spaceCount={spaceCount} available={available} onClear={clear} />;
119
+ };
120
+
121
+ export const SyncCardSurface = () => {
122
+ const { spaces, copy } = useSyncRows();
123
+ return <SyncCard spaces={spaces} onCopy={copy} />;
124
+ };
125
+
126
+ export const IndexerCardSurface = () => {
127
+ const { spaces, refresh, copy } = useIndexerRows();
128
+ return <IndexerCard spaces={spaces} onRefresh={refresh} onCopy={copy} />;
129
+ };
130
+
131
+ /** The slowest queries; the header button shows every query on the devtools Queries page. */
132
+ export const QueriesCardSurface = () => {
133
+ const { queries } = useQueryMetrics();
134
+ const { invokePromise } = Hooks.useOperationInvoker();
135
+ const handleOpen = useCallback(
136
+ () => void invokePromise(DebugOperation.OpenPage, { nodeId: Devtools.getNodePath(Devtools.Echo.Queries) }),
137
+ [invokePromise],
138
+ );
139
+ return <QueriesCard queries={queries} onOpen={handleOpen} />;
140
+ };
@@ -6,13 +6,15 @@
6
6
 
7
7
  import React, { type ComponentType, useCallback } from 'react';
8
8
 
9
- import { useOperationInvoker } from '@dxos/app-framework/ui';
9
+ import * as Hooks from '@dxos/app-framework/Hooks';
10
10
  import * as GraphPath from '@dxos/app-toolkit/GraphPath';
11
+ import * as ToolkitHooks from '@dxos/app-toolkit/Hooks';
11
12
  import * as LayoutOperation from '@dxos/app-toolkit/LayoutOperation';
12
- import { useActiveSpace } from '@dxos/app-toolkit/ui';
13
- import { InvocationTraceContainer, SpaceInfoPanel, SpaceListPanel, TestingPanel } from '@dxos/devtools';
13
+ import { InvocationTraceContainer, SpaceInfoArticle, SpaceListArticle, TestingArticle } from '@dxos/devtools';
14
14
  import { Feed } from '@dxos/echo';
15
+ import { useResolveRef } from '@dxos/echo-react';
15
16
  import { log } from '@dxos/log';
17
+ import * as DebugSurface from '@dxos/plugin-debug/DebugSurface';
16
18
  import * as ScriptOperation from '@dxos/plugin-script/ScriptOperation';
17
19
  import * as SpaceOperation from '@dxos/plugin-space/SpaceOperation';
18
20
  import { type Space } from '@dxos/react-client/echo';
@@ -20,66 +22,58 @@ import { type Space } from '@dxos/react-client/echo';
20
22
  import { Devtools } from '#types';
21
23
 
22
24
  export type ActiveSpacePanelProps = {
23
- /** Stable module-level panel component; the surface passes it through its `props` mapper. */
24
- Panel: ComponentType<{ space: Space }>;
25
+ role?: string;
26
+ /** Stable module-level article component; the surface passes it through its `props` mapper. */
27
+ Panel: ComponentType<{ role?: string; space: Space }>;
25
28
  };
26
29
 
27
30
  /**
28
31
  * Most devtools panels take the active space and render nothing without one. A mapper cannot read
29
32
  * the active space (it is a hook) nor decline to render, so they share this wrapper.
30
33
  */
31
- export const ActiveSpacePanel = ({ Panel }: ActiveSpacePanelProps) => {
32
- const space = useActiveSpace();
34
+ export const ActiveSpacePanel = ({ role, Panel }: ActiveSpacePanelProps) => {
35
+ const space = ToolkitHooks.useActiveSpace();
33
36
 
34
- return space ? <Panel space={space} /> : null;
37
+ return space ? <Panel role={role} space={space} /> : null;
35
38
  };
36
39
 
37
- export const SpaceListSurface = () => {
38
- const { invokePromise } = useOperationInvoker();
39
- const handleSelect = useCallback(
40
- () => invokePromise(LayoutOperation.Open, { subject: [Devtools.Echo.Space] }),
41
- [invokePromise],
42
- );
40
+ export type NavigableSurfaceProps = {
41
+ role?: string;
42
+ onNavigate: DebugSurface.PageData['onNavigate'];
43
+ };
43
44
 
44
- return <SpaceListPanel onSelect={handleSelect} />;
45
+ /** The space list; selecting a space shows its page in the debug panel. */
46
+ export const SpaceListSurface = ({ role, onNavigate }: NavigableSurfaceProps) => {
47
+ const handleSelect = useCallback(() => onNavigate(Devtools.getNodePath(Devtools.Echo.Space)), [onNavigate]);
48
+
49
+ return <SpaceListArticle role={role} onSelect={handleSelect} />;
45
50
  };
46
51
 
47
- export const SpaceInfoSurface = () => {
48
- const space = useActiveSpace();
49
- const { invokePromise } = useOperationInvoker();
50
- const handleSelect = useCallback(
51
- () => invokePromise(LayoutOperation.Open, { subject: [Devtools.Echo.Feeds] }),
52
- [invokePromise],
53
- );
52
+ /** The active space; selecting a feed or pipeline shows the feeds page in the debug panel. */
53
+ export const SpaceInfoSurface = ({ role, onNavigate }: NavigableSurfaceProps) => {
54
+ const space = ToolkitHooks.useActiveSpace();
55
+ const handleSelect = useCallback(() => onNavigate(Devtools.getNodePath(Devtools.Echo.Feeds)), [onNavigate]);
54
56
  if (!space) {
55
57
  return null;
56
58
  }
57
59
 
58
- return <SpaceInfoPanel space={space} onSelectFeed={handleSelect} onSelectPipeline={handleSelect} />;
60
+ return <SpaceInfoArticle role={role} space={space} onSelectFeed={handleSelect} onSelectPipeline={handleSelect} />;
59
61
  };
60
62
 
61
- export const EdgeTracesSurface = () => {
62
- const space = useActiveSpace();
63
+ export const EdgeTracesSurface = ({ role }: { role?: string }) => {
64
+ const space = ToolkitHooks.useActiveSpace();
65
+ const feed = useResolveRef(space?.properties.invocationTraceFeed);
63
66
  if (!space) {
64
67
  return null;
65
68
  }
66
69
 
67
- const feed = space.properties.invocationTraceFeed?.target;
68
70
  const feedDXN = feed ? Feed.getFeedUri(feed) : undefined;
69
71
 
70
- return <InvocationTraceContainer db={space.db} feedDXN={feedDXN} detailAxis='block' />;
72
+ return <InvocationTraceContainer role={role} db={space.db} feedDXN={feedDXN} detailAxis='block' />;
71
73
  };
72
74
 
73
- export const EdgeTestingSurface = () => {
74
- const { invokePromise } = useOperationInvoker();
75
- const onSpaceCreate = useCallback(
76
- async (space: Space) => {
77
- await space.waitUntilReady();
78
- await invokePromise(SpaceOperation.Migrate, { space });
79
- await space.db.flush();
80
- },
81
- [invokePromise],
82
- );
75
+ export const EdgeTestingSurface = ({ role }: { role?: string }) => {
76
+ const { invokePromise } = Hooks.useOperationInvoker();
83
77
  const onScriptPluginOpen = useCallback(
84
78
  async (space: Space) => {
85
79
  await space.waitUntilReady();
@@ -101,5 +95,5 @@ export const EdgeTestingSurface = () => {
101
95
  [invokePromise],
102
96
  );
103
97
 
104
- return <TestingPanel onSpaceCreate={onSpaceCreate} onScriptPluginOpen={onScriptPluginOpen} />;
98
+ return <TestingArticle role={role} onScriptPluginOpen={onScriptPluginOpen} />;
105
99
  };
@@ -0,0 +1,75 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+ import * as Option from 'effect/Option';
7
+ import * as Atom from 'effect/reactivity/Atom';
8
+ import { describe, test } from 'vitest';
9
+
10
+ import * as AppGraphBuilder from '@dxos/app-graph/AppGraphBuilder';
11
+ import * as PathResolution from '@dxos/app-graph/PathResolution';
12
+ import { setupGraphBuilder } from '@dxos/app-graph/testing';
13
+ import * as AppCapabilities from '@dxos/app-toolkit/AppCapabilities';
14
+ import * as GraphPath from '@dxos/app-toolkit/GraphPath';
15
+ import * as EffectEx from '@dxos/effect/EffectEx';
16
+ import * as GraphNode from '@dxos/graph/GraphNode';
17
+ import * as GraphNodeMatcher from '@dxos/graph/GraphNodeMatcher';
18
+
19
+ import { Devtools } from '#types';
20
+
21
+ import { createDevtoolsExtension } from './app-graph-builder.ts';
22
+
23
+ describe('devtools graph extension', () => {
24
+ const setup = async () => {
25
+ // Stand-in for plugin-debug's root extension: root -> hidden debug category.
26
+ const rootExtensions = await EffectEx.runPromise(
27
+ AppGraphBuilder.createExtension({
28
+ id: 'testRoot',
29
+ match: GraphNodeMatcher.whenRoot,
30
+ connector: () =>
31
+ Effect.succeed([
32
+ {
33
+ id: GraphPath.GroupSegments.debug,
34
+ type: GraphPath.GroupTypes.debug,
35
+ data: null,
36
+ properties: { disposition: 'hidden' },
37
+ },
38
+ ]),
39
+ }),
40
+ );
41
+ const devtoolsExtensions = await EffectEx.runPromise(
42
+ createDevtoolsExtension(Atom.make<AppCapabilities.AppGraph[]>([])),
43
+ );
44
+ const context = setupGraphBuilder({ extensions: [...rootExtensions, ...devtoolsExtensions] });
45
+ const debugRootId = `${GraphNode.RootId}/${GraphPath.GroupSegments.debug}`;
46
+ const devtoolsNodeId = GraphNode.qualifyId(debugRootId, Devtools.nodeId(Devtools.id));
47
+ await context.expand(GraphNode.RootId);
48
+ await context.expand(debugRootId);
49
+ await context.expand(devtoolsNodeId);
50
+
51
+ return { ...context, devtoolsNodeId };
52
+ };
53
+
54
+ test('the devtools tree hangs off the debug category', async ({ expect }) => {
55
+ const { getConnections, devtoolsNodeId } = await setup();
56
+ const ids = getConnections(devtoolsNodeId).map((node) => node.id);
57
+ expect(ids).toContain(GraphNode.qualifyId(devtoolsNodeId, Devtools.nodeId(Devtools.AppGraph)));
58
+ expect(ids).toContain(GraphNode.qualifyId(devtoolsNodeId, Devtools.nodeId(Devtools.Client.id)));
59
+ });
60
+
61
+ test('getNodePath names the node the tree builds', async ({ expect }) => {
62
+ const { expand, getNode, devtoolsNodeId } = await setup();
63
+ expect(Devtools.getNodePath(Devtools.id)).toBe(devtoolsNodeId);
64
+ await expand(Devtools.getNodePath(Devtools.Echo.id));
65
+ for (const id of [Devtools.AppGraph, Devtools.Echo.id, Devtools.Echo.Space, Devtools.Echo.Feeds]) {
66
+ expect(getNode(Devtools.getNodePath(id))).not.toBeNull();
67
+ }
68
+ });
69
+
70
+ test('devtools pages have no URL representation', async ({ expect }) => {
71
+ const { builder, devtoolsNodeId } = await setup();
72
+ const pageId = GraphNode.qualifyId(devtoolsNodeId, Devtools.nodeId(Devtools.AppGraph));
73
+ expect(Option.isNone(PathResolution.representNode(builder, pageId))).toBe(true);
74
+ });
75
+ });