@dxos/plugin-devtools 0.10.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 (125) hide show
  1. package/LICENSE +105 -0
  2. package/PLUGIN.mdl +373 -0
  3. package/README.md +15 -0
  4. package/dist/lib/neutral/DebugGraph-F7AMZXKW.mjs +16 -0
  5. package/dist/lib/neutral/DebugGraph-F7AMZXKW.mjs.map +7 -0
  6. package/dist/lib/neutral/DevtoolsOverviewContainer-DCNZFKK5.mjs +51 -0
  7. package/dist/lib/neutral/DevtoolsOverviewContainer-DCNZFKK5.mjs.map +7 -0
  8. package/dist/lib/neutral/DevtoolsPlugin.mjs +55 -0
  9. package/dist/lib/neutral/DevtoolsPlugin.mjs.map +7 -0
  10. package/dist/lib/neutral/DevtoolsPlugin.node.mjs +16 -0
  11. package/dist/lib/neutral/DevtoolsPlugin.node.mjs.map +7 -0
  12. package/dist/lib/neutral/DevtoolsPlugin.workerd.mjs +12 -0
  13. package/dist/lib/neutral/DevtoolsPlugin.workerd.mjs.map +7 -0
  14. package/dist/lib/neutral/GithubPanel-DWFZXZUK.mjs +178 -0
  15. package/dist/lib/neutral/GithubPanel-DWFZXZUK.mjs.map +7 -0
  16. package/dist/lib/neutral/RegistryPanel-CVETZUZV.mjs +162 -0
  17. package/dist/lib/neutral/RegistryPanel-CVETZUZV.mjs.map +7 -0
  18. package/dist/lib/neutral/app-graph-builder-KHZ65O2Q.mjs +560 -0
  19. package/dist/lib/neutral/app-graph-builder-KHZ65O2Q.mjs.map +7 -0
  20. package/dist/lib/neutral/capabilities/index.mjs +13 -0
  21. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  22. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  23. package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
  24. package/dist/lib/neutral/chunk-PJNVAE7D.mjs +81 -0
  25. package/dist/lib/neutral/chunk-PJNVAE7D.mjs.map +7 -0
  26. package/dist/lib/neutral/chunk-QCL4JV4T.mjs +39 -0
  27. package/dist/lib/neutral/chunk-QCL4JV4T.mjs.map +7 -0
  28. package/dist/lib/neutral/containers/index.mjs +15 -0
  29. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  30. package/dist/lib/neutral/index.mjs +12 -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/plugin.mjs +12 -0
  36. package/dist/lib/neutral/plugin.mjs.map +7 -0
  37. package/dist/lib/neutral/react-context-UJURDOO6.mjs +17 -0
  38. package/dist/lib/neutral/react-context-UJURDOO6.mjs.map +7 -0
  39. package/dist/lib/neutral/react-surface-XILLHGRR.mjs +332 -0
  40. package/dist/lib/neutral/react-surface-XILLHGRR.mjs.map +7 -0
  41. package/dist/lib/neutral/translations.mjs +65 -0
  42. package/dist/lib/neutral/translations.mjs.map +7 -0
  43. package/dist/lib/neutral/types/index.mjs +8 -0
  44. package/dist/lib/neutral/types/index.mjs.map +7 -0
  45. package/dist/types/dx.config.d.ts +28 -0
  46. package/dist/types/dx.config.d.ts.map +1 -0
  47. package/dist/types/src/DevtoolsPlugin.d.ts +4 -0
  48. package/dist/types/src/DevtoolsPlugin.d.ts.map +1 -0
  49. package/dist/types/src/DevtoolsPlugin.node.d.ts +4 -0
  50. package/dist/types/src/DevtoolsPlugin.node.d.ts.map +1 -0
  51. package/dist/types/src/DevtoolsPlugin.test.d.ts +2 -0
  52. package/dist/types/src/DevtoolsPlugin.test.d.ts.map +1 -0
  53. package/dist/types/src/DevtoolsPlugin.workerd.d.ts +4 -0
  54. package/dist/types/src/DevtoolsPlugin.workerd.d.ts.map +1 -0
  55. package/dist/types/src/capabilities/app-graph-builder.d.ts +5 -0
  56. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  57. package/dist/types/src/capabilities/index.d.ts +5 -0
  58. package/dist/types/src/capabilities/index.d.ts.map +1 -0
  59. package/dist/types/src/capabilities/react-context.d.ts +5 -0
  60. package/dist/types/src/capabilities/react-context.d.ts.map +1 -0
  61. package/dist/types/src/capabilities/react-surface.d.ts +5 -0
  62. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  63. package/dist/types/src/containers/DebugGraph/DebugGraph.d.ts +11 -0
  64. package/dist/types/src/containers/DebugGraph/DebugGraph.d.ts.map +1 -0
  65. package/dist/types/src/containers/DebugGraph/index.d.ts +2 -0
  66. package/dist/types/src/containers/DebugGraph/index.d.ts.map +1 -0
  67. package/dist/types/src/containers/DevtoolsOverviewContainer/DevtoolsOverviewContainer.d.ts +6 -0
  68. package/dist/types/src/containers/DevtoolsOverviewContainer/DevtoolsOverviewContainer.d.ts.map +1 -0
  69. package/dist/types/src/containers/DevtoolsOverviewContainer/index.d.ts +2 -0
  70. package/dist/types/src/containers/DevtoolsOverviewContainer/index.d.ts.map +1 -0
  71. package/dist/types/src/containers/GithubPanel/GithubComponent.d.ts +15 -0
  72. package/dist/types/src/containers/GithubPanel/GithubComponent.d.ts.map +1 -0
  73. package/dist/types/src/containers/GithubPanel/GithubComponent.stories.d.ts +80 -0
  74. package/dist/types/src/containers/GithubPanel/GithubComponent.stories.d.ts.map +1 -0
  75. package/dist/types/src/containers/GithubPanel/GithubPanel.d.ts +6 -0
  76. package/dist/types/src/containers/GithubPanel/GithubPanel.d.ts.map +1 -0
  77. package/dist/types/src/containers/GithubPanel/index.d.ts +2 -0
  78. package/dist/types/src/containers/GithubPanel/index.d.ts.map +1 -0
  79. package/dist/types/src/containers/RegistryPanel/RegistryPanel.d.ts +6 -0
  80. package/dist/types/src/containers/RegistryPanel/RegistryPanel.d.ts.map +1 -0
  81. package/dist/types/src/containers/RegistryPanel/index.d.ts +2 -0
  82. package/dist/types/src/containers/RegistryPanel/index.d.ts.map +1 -0
  83. package/dist/types/src/containers/index.d.ts +6 -0
  84. package/dist/types/src/containers/index.d.ts.map +1 -0
  85. package/dist/types/src/index.d.ts +3 -0
  86. package/dist/types/src/index.d.ts.map +1 -0
  87. package/dist/types/src/meta.d.ts +33 -0
  88. package/dist/types/src/meta.d.ts.map +1 -0
  89. package/dist/types/src/plugin.d.ts +3 -0
  90. package/dist/types/src/plugin.d.ts.map +1 -0
  91. package/dist/types/src/translations.d.ts +67 -0
  92. package/dist/types/src/translations.d.ts.map +1 -0
  93. package/dist/types/src/types/Devtools.d.ts +56 -0
  94. package/dist/types/src/types/Devtools.d.ts.map +1 -0
  95. package/dist/types/src/types/index.d.ts +2 -0
  96. package/dist/types/src/types/index.d.ts.map +1 -0
  97. package/dist/types/tsconfig.tsbuildinfo +1 -0
  98. package/dx.config.ts +28 -0
  99. package/package.json +153 -0
  100. package/src/DevtoolsPlugin.node.ts +16 -0
  101. package/src/DevtoolsPlugin.test.ts +24 -0
  102. package/src/DevtoolsPlugin.tsx +54 -0
  103. package/src/DevtoolsPlugin.workerd.ts +11 -0
  104. package/src/capabilities/app-graph-builder.ts +388 -0
  105. package/src/capabilities/index.ts +9 -0
  106. package/src/capabilities/react-context.tsx +25 -0
  107. package/src/capabilities/react-surface.tsx +331 -0
  108. package/src/containers/DebugGraph/DebugGraph.tsx +16 -0
  109. package/src/containers/DebugGraph/index.ts +5 -0
  110. package/src/containers/DevtoolsOverviewContainer/DevtoolsOverviewContainer.tsx +52 -0
  111. package/src/containers/DevtoolsOverviewContainer/index.ts +5 -0
  112. package/src/containers/GithubPanel/GithubComponent.stories.tsx +38 -0
  113. package/src/containers/GithubPanel/GithubComponent.tsx +192 -0
  114. package/src/containers/GithubPanel/GithubPanel.tsx +19 -0
  115. package/src/containers/GithubPanel/index.ts +5 -0
  116. package/src/containers/RegistryPanel/RegistryPanel.tsx +152 -0
  117. package/src/containers/RegistryPanel/index.ts +5 -0
  118. package/src/containers/index.ts +10 -0
  119. package/src/index.ts +6 -0
  120. package/src/meta.ts +9 -0
  121. package/src/plugin.ts +9 -0
  122. package/src/translations.ts +66 -0
  123. package/src/types/Devtools.ts +72 -0
  124. package/src/types/index.ts +5 -0
  125. package/src/vite-env.d.ts +10 -0
@@ -0,0 +1,9 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Capability } from '@dxos/app-framework';
6
+
7
+ export const AppGraphBuilder = Capability.lazy('AppGraphBuilder', () => import('./app-graph-builder'));
8
+ export const ReactContext = Capability.lazy('ReactContext', () => import('./react-context'));
9
+ export const ReactSurface = Capability.lazy('ReactSurface', () => import('./react-surface'));
@@ -0,0 +1,25 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+ import React from 'react';
7
+
8
+ import { Capabilities, Capability } from '@dxos/app-framework';
9
+ import { Surface } from '@dxos/app-framework/ui';
10
+ import { DevtoolsContextProvider } from '@dxos/devtools';
11
+
12
+ import { meta } from '#meta';
13
+
14
+ export default Capability.makeModule(() =>
15
+ Effect.succeed(
16
+ Capability.contributes(Capabilities.ReactContext, {
17
+ id: meta.profile.key,
18
+ context: ({ children }) => (
19
+ <Surface.ProfilerProvider>
20
+ <DevtoolsContextProvider>{children}</DevtoolsContextProvider>
21
+ </Surface.ProfilerProvider>
22
+ ),
23
+ }),
24
+ ),
25
+ );
@@ -0,0 +1,331 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+ import React, { useCallback } from 'react';
7
+
8
+ import { Capabilities, Capability } from '@dxos/app-framework';
9
+ import { Surface, useOperationInvoker } from '@dxos/app-framework/ui';
10
+ import { LayoutOperation, Paths } from '@dxos/app-toolkit';
11
+ import { AppSurface, useActiveSpace } from '@dxos/app-toolkit/ui';
12
+ import { EDGE_SERVICE_DEFAULTS, EdgeServiceName } from '@dxos/config';
13
+ import {
14
+ AutomergePanel,
15
+ ConfigPanel,
16
+ CredentialsPanel,
17
+ DeviceListPanel,
18
+ DiagnosticsPanel,
19
+ EdgeDashboardPanel,
20
+ FeedsPanel,
21
+ IdentityPanel,
22
+ InvocationTraceContainer,
23
+ KeyringPanel,
24
+ LoggingPanel,
25
+ MembersPanel,
26
+ MetadataPanel,
27
+ NetworkPanel,
28
+ ObjectsPanel,
29
+ QueuesPanel,
30
+ SchemaPanel,
31
+ SignalPanel,
32
+ SpaceInfoPanel,
33
+ SpaceListPanel,
34
+ SqlitePanel,
35
+ StoragePanel,
36
+ SwarmPanel,
37
+ TestingPanel,
38
+ WorkflowPanel,
39
+ } from '@dxos/devtools';
40
+ import { Feed } from '@dxos/echo';
41
+ import { log } from '@dxos/log';
42
+ import { type Graph } from '@dxos/plugin-graph';
43
+ import { ScriptOperation } from '@dxos/plugin-script';
44
+ import { SpaceOperation } from '@dxos/plugin-space';
45
+ import { type Space } from '@dxos/react-client/echo';
46
+ import { ToolsExplorer } from '@dxos/react-ui-introspect';
47
+
48
+ import { DebugGraph, DevtoolsOverviewContainer, RegistryPanel } from '#containers';
49
+ import { Devtools } from '#types';
50
+
51
+ const MCP_SERVER_URL = EDGE_SERVICE_DEFAULTS[EdgeServiceName.Introspect];
52
+
53
+ type GraphDebug = {
54
+ graph: Graph.Graph;
55
+ root: string;
56
+ };
57
+
58
+ const isGraphDebug = (data: any): data is GraphDebug => {
59
+ const graph = data?.graph;
60
+ return (
61
+ graph != null && typeof graph === 'object' && typeof graph.json === 'function' && typeof data?.root === 'string'
62
+ );
63
+ };
64
+
65
+ export default Capability.makeModule(
66
+ Effect.fnUntraced(function* () {
67
+ return Capability.contributes(Capabilities.ReactSurface, [
68
+ Surface.create({
69
+ id: 'appGraph',
70
+ filter: AppSurface.subject(AppSurface.Article, isGraphDebug),
71
+ component: ({ data }) => <DebugGraph graph={data.subject.graph} root={data.subject.root} />,
72
+ }),
73
+ Surface.create({
74
+ id: 'toolsExplorer',
75
+ filter: AppSurface.literal(AppSurface.Article, Devtools.ToolsExplorer),
76
+ component: () => <ToolsExplorer serverUrl={MCP_SERVER_URL} />,
77
+ }),
78
+ Surface.create({
79
+ id: 'registry',
80
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Echo.Registry),
81
+ component: () => <RegistryPanel />,
82
+ }),
83
+ Surface.create({
84
+ id: 'devtoolsOverview',
85
+ filter: Surface.makeFilter(AppSurface.deckCompanion('devtoolsOverview')),
86
+ component: () => <DevtoolsOverviewContainer />,
87
+ }),
88
+
89
+ //
90
+ // Devtools
91
+ //
92
+
93
+ Surface.create({
94
+ id: 'client.config',
95
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Client.Config),
96
+ component: () => <ConfigPanel vaultSelector={false} />,
97
+ }),
98
+ Surface.create({
99
+ id: 'client.storage',
100
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Client.Storage),
101
+ component: () => <StoragePanel />,
102
+ }),
103
+ Surface.create({
104
+ id: 'client.sqlite',
105
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Client.Sqlite),
106
+ component: () => <SqlitePanel />,
107
+ }),
108
+ Surface.create({
109
+ id: 'client.logs',
110
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Client.Logs),
111
+ component: () => <LoggingPanel />,
112
+ }),
113
+ Surface.create({
114
+ id: 'client.diagnostics',
115
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Client.Diagnostics),
116
+ component: () => <DiagnosticsPanel />,
117
+ }),
118
+ Surface.create({
119
+ id: 'halo.identity',
120
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Halo.Identity),
121
+ component: () => <IdentityPanel />,
122
+ }),
123
+ Surface.create({
124
+ id: 'halo.devices',
125
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Halo.Devices),
126
+ component: () => <DeviceListPanel />,
127
+ }),
128
+ Surface.create({
129
+ id: 'halo.keyring',
130
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Halo.Keyring),
131
+ component: () => <KeyringPanel />,
132
+ }),
133
+ Surface.create({
134
+ id: 'halo.credentials',
135
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Halo.Credentials),
136
+ component: () => {
137
+ const space = useActiveSpace();
138
+ if (!space) {
139
+ return null;
140
+ }
141
+
142
+ return <CredentialsPanel space={space} />;
143
+ },
144
+ }),
145
+ Surface.create({
146
+ id: 'echo.spaces',
147
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Echo.Spaces),
148
+ component: () => {
149
+ const { invokePromise } = useOperationInvoker();
150
+ const handleSelect = useCallback(
151
+ () => invokePromise(LayoutOperation.Open, { subject: [Devtools.Echo.Space] }),
152
+ [invokePromise],
153
+ );
154
+ return <SpaceListPanel onSelect={handleSelect} />;
155
+ },
156
+ }),
157
+ Surface.create({
158
+ id: 'echo.space',
159
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Echo.Space),
160
+ component: () => {
161
+ const space = useActiveSpace();
162
+ const { invokePromise } = useOperationInvoker();
163
+ const handleSelect = useCallback(
164
+ () => invokePromise(LayoutOperation.Open, { subject: [Devtools.Echo.Feeds] }),
165
+ [invokePromise],
166
+ );
167
+ if (!space) {
168
+ return null;
169
+ }
170
+
171
+ return <SpaceInfoPanel space={space} onSelectFeed={handleSelect} onSelectPipeline={handleSelect} />;
172
+ },
173
+ }),
174
+ Surface.create({
175
+ id: 'echo.feeds',
176
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Echo.Feeds),
177
+ component: () => {
178
+ const space = useActiveSpace();
179
+ if (!space) {
180
+ return null;
181
+ }
182
+
183
+ return <FeedsPanel space={space} />;
184
+ },
185
+ }),
186
+ Surface.create({
187
+ id: 'echo.objects',
188
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Echo.Objects),
189
+ component: () => {
190
+ const space = useActiveSpace();
191
+ if (!space) {
192
+ return null;
193
+ }
194
+
195
+ return <ObjectsPanel space={space} />;
196
+ },
197
+ }),
198
+ Surface.create({
199
+ id: 'echo.schema',
200
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Echo.Schema),
201
+ component: () => {
202
+ const space = useActiveSpace();
203
+ if (!space) {
204
+ return null;
205
+ }
206
+
207
+ return <SchemaPanel space={space} />;
208
+ },
209
+ }),
210
+ Surface.create({
211
+ id: 'echo.automerge',
212
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Echo.Automerge),
213
+ component: () => {
214
+ const space = useActiveSpace();
215
+ if (!space) {
216
+ return null;
217
+ }
218
+
219
+ return <AutomergePanel space={space} />;
220
+ },
221
+ }),
222
+ Surface.create({
223
+ id: 'echo.queues',
224
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Echo.Queues),
225
+ component: () => <QueuesPanel />,
226
+ }),
227
+ Surface.create({
228
+ id: 'echo.members',
229
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Echo.Members),
230
+ component: () => {
231
+ const space = useActiveSpace();
232
+ if (!space) {
233
+ return null;
234
+ }
235
+
236
+ return <MembersPanel space={space} />;
237
+ },
238
+ }),
239
+ Surface.create({
240
+ id: 'echo.metadata',
241
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Echo.Metadata),
242
+ component: () => <MetadataPanel />,
243
+ }),
244
+ Surface.create({
245
+ id: 'mesh.signal',
246
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Mesh.Signal),
247
+ component: () => <SignalPanel />,
248
+ }),
249
+ Surface.create({
250
+ id: 'mesh.swarm',
251
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Mesh.Swarm),
252
+ component: () => <SwarmPanel />,
253
+ }),
254
+ Surface.create({
255
+ id: 'mesh.network',
256
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Mesh.Network),
257
+ component: () => {
258
+ const space = useActiveSpace();
259
+ if (!space) {
260
+ return null;
261
+ }
262
+
263
+ return <NetworkPanel space={space} />;
264
+ },
265
+ }),
266
+ Surface.create({
267
+ id: 'edge.dashboard',
268
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Edge.Dashboard),
269
+ component: () => <EdgeDashboardPanel />,
270
+ }),
271
+ Surface.create({
272
+ id: 'edge.workflows',
273
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Edge.Workflows),
274
+ component: () => {
275
+ const space = useActiveSpace();
276
+ if (!space) {
277
+ return null;
278
+ }
279
+
280
+ return <WorkflowPanel space={space} />;
281
+ },
282
+ }),
283
+ Surface.create({
284
+ id: 'edge.traces',
285
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Edge.Traces),
286
+ component: () => {
287
+ const space = useActiveSpace();
288
+ if (!space) {
289
+ return null;
290
+ }
291
+
292
+ const feed = space.properties.invocationTraceFeed?.target;
293
+ const feedDXN = feed ? Feed.getFeedUri(feed) : undefined;
294
+ return <InvocationTraceContainer db={space.db} feedDXN={feedDXN} detailAxis='block' />;
295
+ },
296
+ }),
297
+ Surface.create({
298
+ id: 'edge.testing',
299
+ filter: AppSurface.literal(AppSurface.Article, Devtools.Edge.Testing),
300
+ component: () => {
301
+ const { invokePromise } = useOperationInvoker();
302
+ const onSpaceCreate = useCallback(
303
+ async (space: Space) => {
304
+ await space.waitUntilReady();
305
+ await invokePromise(SpaceOperation.Migrate, { space });
306
+ await space.db.flush();
307
+ },
308
+ [invokePromise],
309
+ );
310
+ const onScriptPluginOpen = useCallback(
311
+ async (space: Space) => {
312
+ await space.waitUntilReady();
313
+ const createResult = await invokePromise(ScriptOperation.CreateScript, { db: space.db });
314
+ if (createResult.data?.object) {
315
+ await invokePromise(SpaceOperation.AddObject, { target: space.db, object: createResult.data.object });
316
+ }
317
+ log.info('script created', { result: createResult });
318
+ if (createResult.data?.object) {
319
+ await invokePromise(LayoutOperation.Open, {
320
+ subject: [Paths.getObjectPathFromObject(createResult.data.object)],
321
+ });
322
+ }
323
+ },
324
+ [invokePromise],
325
+ );
326
+ return <TestingPanel onSpaceCreate={onSpaceCreate} onScriptPluginOpen={onScriptPluginOpen} />;
327
+ },
328
+ }),
329
+ ]);
330
+ }),
331
+ );
@@ -0,0 +1,16 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { Tree } from '@dxos/devtools';
8
+ import { Graph } from '@dxos/plugin-graph';
9
+
10
+ export type DebugGraphProps = { graph: Graph.Graph; root: string };
11
+
12
+ export const DebugGraph = ({ graph, root }: DebugGraphProps) => {
13
+ return <Tree data={Graph.toJSON(graph, root)} />;
14
+ };
15
+
16
+ DebugGraph.displayName = 'DebugGraph';
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export { DebugGraph as default } from './DebugGraph';
@@ -0,0 +1,52 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { useCallback, useMemo } from 'react';
6
+
7
+ import { Surface } from '@dxos/app-framework/ui';
8
+ import { AppSurface } from '@dxos/app-toolkit/ui';
9
+ import { StatsPanel, useStats } from '@dxos/devtools';
10
+
11
+ export const DevtoolsOverviewContainer = () => {
12
+ const [stats, refreshStats] = useStats();
13
+ const surfaceProfilerStats = Surface.useProfilerStats();
14
+ const surfaceMetrics = Surface.useMetrics();
15
+ const clearSurfaceProfiler = Surface.useProfilerClear();
16
+
17
+ // The panel joins profiler stats with dispatch metrics, so reset must clear both.
18
+ const handleClearSurfaceProfiler = useCallback(() => {
19
+ clearSurfaceProfiler?.();
20
+ Surface.clearMetrics();
21
+ }, [clearSurfaceProfiler]);
22
+
23
+ // Join dispatch metrics onto the render-timing stats (both keyed by `surface/<id>/<role>`).
24
+ const enrichedStats = useMemo(() => {
25
+ const byId = new Map(surfaceMetrics.map((metric) => [metric.id, metric]));
26
+ return surfaceProfilerStats.map((stat) => {
27
+ const metric = byId.get(stat.id);
28
+ return metric
29
+ ? {
30
+ ...stat,
31
+ candidates: metric.candidates,
32
+ truncated: metric.truncated,
33
+ errors: metric.errors,
34
+ dataUnstable: metric.dataUnstable,
35
+ }
36
+ : stat;
37
+ });
38
+ }, [surfaceProfilerStats, surfaceMetrics]);
39
+
40
+ return (
41
+ <StatsPanel
42
+ stats={stats}
43
+ surfaceProfilerStats={enrichedStats}
44
+ onRefresh={refreshStats}
45
+ onClearSurfaceProfiler={handleClearSurfaceProfiler}
46
+ >
47
+ <Surface.Surface type={AppSurface.DevtoolsOverview} />
48
+ </StatsPanel>
49
+ );
50
+ };
51
+
52
+ DevtoolsOverviewContainer.displayName = 'DevtoolsOverviewContainer';
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export { DevtoolsOverviewContainer as default } from './DevtoolsOverviewContainer';
@@ -0,0 +1,38 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React from 'react';
7
+
8
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
9
+
10
+ import { translations } from '#translations';
11
+
12
+ import { GithubComponent } from './GithubComponent';
13
+
14
+ const DefaultStory = () => (
15
+ <GithubComponent.Root>
16
+ <div className='grid grid-rows-[auto_minmax(0,1fr)_auto] overflow-hidden h-full is-full'>
17
+ <GithubComponent.Header />
18
+ <GithubComponent.Content />
19
+ <GithubComponent.StatusBar />
20
+ </div>
21
+ </GithubComponent.Root>
22
+ );
23
+
24
+ const meta = {
25
+ title: 'plugins/plugin-devtools/containers/GithubComponent',
26
+ component: DefaultStory,
27
+ decorators: [withTheme(), withLayout({ layout: 'column', classNames: 'w-(--dx-r1-size)' })],
28
+ parameters: {
29
+ layout: 'fullscreen',
30
+ translations,
31
+ },
32
+ } satisfies Meta<typeof DefaultStory>;
33
+
34
+ export default meta;
35
+
36
+ type Story = StoryObj<typeof meta>;
37
+
38
+ export const Default: Story = {};
@@ -0,0 +1,192 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import React, { type ReactNode, createContext, useContext, useEffect, useState } from 'react';
6
+
7
+ import { IconButton, ScrollArea, useTranslation } from '@dxos/react-ui';
8
+
9
+ import { meta } from '#meta';
10
+
11
+ const DEFAULT_REPO = 'dxos/dxos';
12
+ const DEFAULT_LIMIT = 20;
13
+
14
+ type GithubUser = {
15
+ login: string;
16
+ avatar_url: string;
17
+ html_url: string;
18
+ };
19
+
20
+ type GithubPullRequest = {
21
+ number: number;
22
+ title: string;
23
+ html_url: string;
24
+ merged_at: string | null;
25
+ user: GithubUser;
26
+ };
27
+
28
+ type ComponentContextValue = {
29
+ repo: string;
30
+ pulls: GithubPullRequest[];
31
+ unavailable: boolean;
32
+ };
33
+
34
+ const ComponentContext = createContext<ComponentContextValue | null>(null);
35
+
36
+ const useComponentContext = () => {
37
+ const ctx = useContext(ComponentContext);
38
+ if (!ctx) {
39
+ throw new Error('GithubComponent.* parts must be rendered inside GithubComponent.Root.');
40
+ }
41
+ return ctx;
42
+ };
43
+
44
+ export type GithubComponentRootProps = {
45
+ /** `<owner>/<name>`. Defaults to `dxos/dxos`. */
46
+ repo?: string;
47
+ /** Maximum number of merged PRs to fetch. */
48
+ limit?: number;
49
+ children?: ReactNode;
50
+ };
51
+
52
+ const Root = ({ repo = DEFAULT_REPO, limit = DEFAULT_LIMIT, children }: GithubComponentRootProps) => {
53
+ const [pulls, setPulls] = useState<GithubPullRequest[]>([]);
54
+ const [unavailable, setUnavailable] = useState(false);
55
+
56
+ useEffect(() => {
57
+ const controller = new AbortController();
58
+ setPulls([]);
59
+ setUnavailable(false);
60
+ void (async () => {
61
+ try {
62
+ const url = new URL(`https://api.github.com/repos/${repo}/pulls`);
63
+ url.searchParams.set('state', 'closed');
64
+ url.searchParams.set('sort', 'updated');
65
+ url.searchParams.set('direction', 'desc');
66
+ url.searchParams.set('per_page', String(limit * 2));
67
+ const response = await fetch(url.toString(), {
68
+ signal: controller.signal,
69
+ headers: { Accept: 'application/vnd.github+json' },
70
+ });
71
+ if (!response.ok) {
72
+ setUnavailable(true);
73
+ return;
74
+ }
75
+ const items = (await response.json()) as GithubPullRequest[];
76
+ setPulls(items.filter((pull) => pull.merged_at !== null).slice(0, limit));
77
+ setUnavailable(false);
78
+ } catch {
79
+ if (controller.signal.aborted) {
80
+ return;
81
+ }
82
+ setPulls([]);
83
+ setUnavailable(true);
84
+ }
85
+ })();
86
+ return () => controller.abort();
87
+ }, [repo, limit]);
88
+
89
+ return <ComponentContext.Provider value={{ repo, pulls, unavailable }}>{children}</ComponentContext.Provider>;
90
+ };
91
+
92
+ const Header = () => {
93
+ const { t } = useTranslation(meta.profile.key);
94
+ const { repo, pulls, unavailable } = useComponentContext();
95
+ return (
96
+ <header className='flex items-center justify-between gap-1 px-4 py-3 bg-modal-surface border-b border-subdued-separator'>
97
+ <a
98
+ href={`https://github.com/${repo}`}
99
+ target='_blank'
100
+ rel='noopener noreferrer'
101
+ className='text-sm font-medium truncate'
102
+ >
103
+ @{repo}
104
+ </a>
105
+ <div className='text-xs text-description'>
106
+ {unavailable
107
+ ? t('github-unavailable.message')
108
+ : pulls.length > 0
109
+ ? t('recent-prs.label', { count: pulls.length })
110
+ : t('github-loading.message')}
111
+ </div>
112
+ </header>
113
+ );
114
+ };
115
+
116
+ const RELATIVE_UNITS: Array<[Intl.RelativeTimeFormatUnit, number]> = [
117
+ ['year', 60 * 60 * 24 * 365],
118
+ ['month', 60 * 60 * 24 * 30],
119
+ ['week', 60 * 60 * 24 * 7],
120
+ ['day', 60 * 60 * 24],
121
+ ['hour', 60 * 60],
122
+ ['minute', 60],
123
+ ];
124
+
125
+ const formatRelative = (iso: string): string => {
126
+ const formatter = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' });
127
+ const diffSeconds = (Date.parse(iso) - Date.now()) / 1000;
128
+ const absDiff = Math.abs(diffSeconds);
129
+ for (const [unit, secondsInUnit] of RELATIVE_UNITS) {
130
+ if (absDiff >= secondsInUnit) {
131
+ return formatter.format(Math.round(diffSeconds / secondsInUnit), unit);
132
+ }
133
+ }
134
+ return formatter.format(Math.round(diffSeconds), 'second');
135
+ };
136
+
137
+ const PullRow = ({ pull }: { pull: GithubPullRequest }) => (
138
+ <li>
139
+ <a
140
+ href={pull.html_url}
141
+ target='_blank'
142
+ rel='noopener noreferrer'
143
+ className='flex items-start gap-2 px-2 py-1 rounded-sm hover:bg-hover-surface'
144
+ >
145
+ <img src={pull.user.avatar_url} alt='' className='w-6 h-6 rounded-full shrink-0 mt-0.5' />
146
+ <div className='flex flex-col min-w-0 flex-1'>
147
+ <span className='text-sm truncate'>{pull.title}</span>
148
+ <span className='text-xs text-description truncate'>
149
+ #{pull.number} · {pull.user.login} · {pull.merged_at ? formatRelative(pull.merged_at) : ''}
150
+ </span>
151
+ </div>
152
+ </a>
153
+ </li>
154
+ );
155
+
156
+ const Content = () => {
157
+ const { pulls } = useComponentContext();
158
+ return (
159
+ <ScrollArea.Root orientation='vertical'>
160
+ <ScrollArea.Viewport>
161
+ <ul className='flex flex-col p-1'>
162
+ {pulls.map((pull) => (
163
+ <PullRow key={pull.number} pull={pull} />
164
+ ))}
165
+ </ul>
166
+ </ScrollArea.Viewport>
167
+ </ScrollArea.Root>
168
+ );
169
+ };
170
+
171
+ const StatusBar = () => {
172
+ const { t } = useTranslation(meta.profile.key);
173
+ const { repo } = useComponentContext();
174
+ return (
175
+ <IconButton
176
+ icon='ph--github-logo--regular'
177
+ label={t('view-on-github.button')}
178
+ variant='primary'
179
+ classNames='w-full'
180
+ onClick={() => {
181
+ window.open(`https://github.com/${repo}`, '_blank', 'noopener,noreferrer');
182
+ }}
183
+ />
184
+ );
185
+ };
186
+
187
+ export const GithubComponent = {
188
+ Root,
189
+ Header,
190
+ Content,
191
+ StatusBar,
192
+ };
@@ -0,0 +1,19 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { GithubComponent } from './GithubComponent';
8
+
9
+ export const GithubPanel = () => (
10
+ <GithubComponent.Root>
11
+ <div className='h-full grid grid-rows-[auto_minmax(0,1fr)_auto] overflow-hidden h-full is-full'>
12
+ <GithubComponent.Header />
13
+ <GithubComponent.Content />
14
+ <GithubComponent.StatusBar />
15
+ </div>
16
+ </GithubComponent.Root>
17
+ );
18
+
19
+ GithubPanel.displayName = 'GithubPanel';