@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.
- package/PLUGIN.mdl +144 -57
- package/dist/lib/Devtools.mjs +9 -1
- package/dist/lib/Devtools.mjs.map +1 -1
- package/dist/lib/capabilities.browser.mjs +45 -0
- package/dist/lib/capabilities.browser.mjs.map +1 -0
- package/dist/lib/capabilities.mjs +9 -7
- package/dist/lib/capabilities.mjs.map +1 -1
- package/dist/lib/capabilities.node.mjs +7 -3
- package/dist/lib/capabilities.node.mjs.map +1 -1
- package/dist/lib/capabilities.tauri.mjs +45 -0
- package/dist/lib/capabilities.tauri.mjs.map +1 -0
- package/dist/lib/capabilities.workerd.mjs +2 -2
- package/dist/lib/capabilities.workerd.mjs.map +1 -1
- package/dist/lib/{chunk-CliPanel.mjs → chunk-CliArticle.mjs} +28 -24
- package/dist/lib/chunk-CliArticle.mjs.map +1 -0
- package/dist/lib/chunk-DebugGraph.mjs +12 -8
- package/dist/lib/chunk-DebugGraph.mjs.map +1 -1
- package/dist/lib/chunk-DevtoolsCards.mjs +108 -0
- package/dist/lib/chunk-DevtoolsCards.mjs.map +1 -0
- package/dist/lib/chunk-DevtoolsOverviewContainer.mjs +59 -22
- package/dist/lib/chunk-DevtoolsOverviewContainer.mjs.map +1 -1
- package/dist/lib/chunk-GithubPanel.mjs +13 -10
- package/dist/lib/chunk-GithubPanel.mjs.map +1 -1
- package/dist/lib/chunk-PLUGIN.mjs +6 -0
- package/dist/lib/chunk-PLUGIN.mjs.map +1 -0
- package/dist/lib/{chunk-RegistryPanel.mjs → chunk-RegistryArticle.mjs} +26 -25
- package/dist/lib/chunk-RegistryArticle.mjs.map +1 -0
- package/dist/lib/chunk-ToolsExplorerContainer.mjs +7 -3
- package/dist/lib/chunk-ToolsExplorerContainer.mjs.map +1 -1
- package/dist/lib/chunk-app-graph-builder.mjs +323 -312
- package/dist/lib/chunk-app-graph-builder.mjs.map +1 -1
- package/dist/lib/chunk-react-context.mjs +1 -1
- package/dist/lib/chunk-react-context.mjs.map +1 -1
- package/dist/lib/chunk-react-surface.mjs +244 -88
- package/dist/lib/chunk-react-surface.mjs.map +1 -1
- package/dist/lib/containers.mjs +3 -3
- package/dist/lib/containers.mjs.map +1 -1
- package/dist/lib/index.mjs +0 -1
- package/dist/lib/meta.mjs +2 -2
- package/dist/lib/meta.mjs.map +1 -1
- package/dist/lib/translations.mjs +55 -49
- package/dist/lib/translations.mjs.map +1 -1
- package/dist/types/src/capabilities/DevtoolsCards.d.ts +28 -0
- package/dist/types/src/capabilities/DevtoolsCards.d.ts.map +1 -0
- package/dist/types/src/capabilities/DevtoolsSurfaces.d.ts +19 -6
- package/dist/types/src/capabilities/DevtoolsSurfaces.d.ts.map +1 -1
- package/dist/types/src/capabilities/app-graph-builder.d.ts +19 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/capabilities/app-graph-builder.test.d.ts +2 -0
- package/dist/types/src/capabilities/app-graph-builder.test.d.ts.map +1 -0
- package/dist/types/src/capabilities/gen/browser.d.ts +8 -0
- package/dist/types/src/capabilities/gen/browser.d.ts.map +1 -0
- package/dist/types/src/capabilities/gen/tauri.d.ts +8 -0
- package/dist/types/src/capabilities/gen/tauri.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/containers/CliArticle/CliArticle.d.ts +8 -0
- package/dist/types/src/containers/CliArticle/CliArticle.d.ts.map +1 -0
- package/dist/types/src/containers/{CliPanel/CliPanel.stories.d.ts → CliArticle/CliArticle.stories.d.ts} +33 -5
- package/dist/types/src/containers/CliArticle/CliArticle.stories.d.ts.map +1 -0
- package/dist/types/src/containers/CliArticle/index.d.ts +2 -0
- package/dist/types/src/containers/CliArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/{CliPanel → CliArticle}/useCliApp.d.ts +1 -1
- package/dist/types/src/containers/CliArticle/useCliApp.d.ts.map +1 -0
- package/dist/types/src/containers/DebugGraph/DebugGraph.d.ts +2 -1
- package/dist/types/src/containers/DebugGraph/DebugGraph.d.ts.map +1 -1
- package/dist/types/src/containers/DebugGraph/index.d.ts +1 -1
- package/dist/types/src/containers/DebugGraph/index.d.ts.map +1 -1
- package/dist/types/src/containers/DevtoolsOverviewContainer/DevtoolsOverviewContainer.d.ts.map +1 -1
- package/dist/types/src/containers/DevtoolsOverviewContainer/index.d.ts +1 -1
- package/dist/types/src/containers/DevtoolsOverviewContainer/index.d.ts.map +1 -1
- package/dist/types/src/containers/GithubPanel/GithubComponent.d.ts.map +1 -1
- package/dist/types/src/containers/GithubPanel/GithubComponent.stories.d.ts +30 -2
- package/dist/types/src/containers/GithubPanel/GithubComponent.stories.d.ts.map +1 -1
- package/dist/types/src/containers/GithubPanel/index.d.ts +1 -1
- package/dist/types/src/containers/GithubPanel/index.d.ts.map +1 -1
- package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts +8 -0
- package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts.map +1 -0
- package/dist/types/src/containers/RegistryArticle/index.d.ts +2 -0
- package/dist/types/src/containers/RegistryArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/ToolsExplorerContainer/ToolsExplorerContainer.d.ts +3 -1
- package/dist/types/src/containers/ToolsExplorerContainer/ToolsExplorerContainer.d.ts.map +1 -1
- package/dist/types/src/containers/ToolsExplorerContainer/index.d.ts +1 -1
- package/dist/types/src/containers/ToolsExplorerContainer/index.d.ts.map +1 -1
- package/dist/types/src/containers/index.d.ts +2 -2
- package/dist/types/src/containers/index.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +30 -2
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Devtools.d.ts +6 -0
- package/dist/types/src/types/Devtools.d.ts.map +1 -1
- package/dist/types/src/types/index.d.ts +2 -2
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dx.config.ts +2 -2
- package/package.json +43 -39
- package/src/capabilities/DevtoolsCards.tsx +140 -0
- package/src/capabilities/DevtoolsSurfaces.tsx +32 -38
- package/src/capabilities/app-graph-builder.test.ts +75 -0
- package/src/capabilities/app-graph-builder.ts +324 -313
- package/src/capabilities/gen/browser.ts +61 -0
- package/src/capabilities/gen/node.ts +4 -4
- package/src/capabilities/gen/tauri.ts +61 -0
- package/src/capabilities/gen/workerd.ts +2 -2
- package/src/capabilities/index.ts +7 -7
- package/src/capabilities/react-context.tsx +1 -1
- package/src/capabilities/react-surface.ts +222 -79
- package/src/containers/{CliPanel/CliPanel.stories.tsx → CliArticle/CliArticle.stories.tsx} +9 -10
- package/src/containers/{CliPanel/CliPanel.tsx → CliArticle/CliArticle.tsx} +7 -7
- package/src/containers/CliArticle/index.ts +5 -0
- package/src/containers/{CliPanel → CliArticle}/useCliApp.ts +17 -12
- package/src/containers/DebugGraph/DebugGraph.tsx +7 -6
- package/src/containers/DebugGraph/index.ts +1 -1
- package/src/containers/DevtoolsOverviewContainer/DevtoolsOverviewContainer.tsx +72 -32
- package/src/containers/DevtoolsOverviewContainer/index.ts +1 -1
- package/src/containers/GithubPanel/GithubComponent.stories.tsx +2 -2
- package/src/containers/GithubPanel/GithubComponent.tsx +14 -11
- package/src/containers/GithubPanel/GithubPanel.tsx +2 -2
- package/src/containers/GithubPanel/index.ts +1 -1
- package/src/containers/{RegistryPanel/RegistryPanel.tsx → RegistryArticle/RegistryArticle.tsx} +11 -10
- package/src/containers/RegistryArticle/index.ts +5 -0
- package/src/containers/ToolsExplorerContainer/ToolsExplorerContainer.tsx +10 -3
- package/src/containers/ToolsExplorerContainer/index.ts +1 -1
- package/src/containers/index.ts +6 -6
- package/src/index.ts +1 -2
- package/src/meta.ts +1 -1
- package/src/plugin.test.ts +2 -2
- package/src/translations.ts +7 -3
- package/src/types/Devtools.ts +10 -0
- package/src/types/index.ts +2 -2
- package/dist/lib/chunk-CliPanel.mjs.map +0 -1
- package/dist/lib/chunk-RegistryPanel.mjs.map +0 -1
- package/dist/types/src/containers/CliPanel/CliPanel.d.ts +0 -6
- package/dist/types/src/containers/CliPanel/CliPanel.d.ts.map +0 -1
- package/dist/types/src/containers/CliPanel/CliPanel.stories.d.ts.map +0 -1
- package/dist/types/src/containers/CliPanel/index.d.ts +0 -2
- package/dist/types/src/containers/CliPanel/index.d.ts.map +0 -1
- package/dist/types/src/containers/CliPanel/useCliApp.d.ts.map +0 -1
- package/dist/types/src/containers/RegistryPanel/RegistryPanel.d.ts +0 -6
- package/dist/types/src/containers/RegistryPanel/RegistryPanel.d.ts.map +0 -1
- package/dist/types/src/containers/RegistryPanel/index.d.ts +0 -2
- package/dist/types/src/containers/RegistryPanel/index.d.ts.map +0 -1
- package/src/containers/CliPanel/index.ts +0 -5
- package/src/containers/RegistryPanel/index.ts +0 -5
|
@@ -9,18 +9,18 @@ import { userEvent } from 'storybook/test';
|
|
|
9
9
|
import { withPluginManager } from '@dxos/app-framework/testing';
|
|
10
10
|
import { ClientPlugin, initializeIdentity } from '@dxos/plugin-client/testing';
|
|
11
11
|
import * as SpacePlugin from '@dxos/plugin-space/SpacePlugin';
|
|
12
|
-
import
|
|
12
|
+
import * as CorePlugins from '@dxos/plugin-testing/CorePlugins';
|
|
13
13
|
import * as StorybookPlugin from '@dxos/plugin-testing/StorybookPlugin';
|
|
14
14
|
import { runCommand, waitForTerminal } from '@dxos/react-ui-terminal/testing';
|
|
15
15
|
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
16
16
|
|
|
17
17
|
import { translations } from '#translations';
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import { CliArticle } from './CliArticle.tsx';
|
|
20
20
|
|
|
21
21
|
const meta = {
|
|
22
|
-
title: 'plugins/plugin-devtools/containers/
|
|
23
|
-
component:
|
|
22
|
+
title: 'plugins/plugin-devtools/containers/CliArticle',
|
|
23
|
+
component: CliArticle,
|
|
24
24
|
decorators: [
|
|
25
25
|
withLayout({ layout: 'fullscreen' }),
|
|
26
26
|
withTheme(),
|
|
@@ -28,7 +28,7 @@ const meta = {
|
|
|
28
28
|
// No setup event: the panel fires `CommandsRequested` itself, which is what pulls the
|
|
29
29
|
// plugins' command modules in — the same path the app takes.
|
|
30
30
|
plugins: [
|
|
31
|
-
...
|
|
31
|
+
...CorePlugins.make(),
|
|
32
32
|
// The identity brings a default space, which is what the commands resolve against.
|
|
33
33
|
ClientPlugin.make({ onClientInitialized: ({ client }) => Effect.asVoid(initializeIdentity(client)) }),
|
|
34
34
|
StorybookPlugin.make({}),
|
|
@@ -42,7 +42,7 @@ const meta = {
|
|
|
42
42
|
controls: { disable: true },
|
|
43
43
|
translations,
|
|
44
44
|
},
|
|
45
|
-
} satisfies Meta<typeof
|
|
45
|
+
} satisfies Meta<typeof CliArticle>;
|
|
46
46
|
|
|
47
47
|
export default meta;
|
|
48
48
|
|
|
@@ -56,20 +56,19 @@ export const Default: Story = {};
|
|
|
56
56
|
*/
|
|
57
57
|
export const Spec: Story = {
|
|
58
58
|
play: async ({ canvasElement }) => {
|
|
59
|
-
const keyboard = (text: string) => userEvent.keyboard(text);
|
|
60
59
|
await waitForTerminal(canvasElement, 'dx>');
|
|
61
60
|
|
|
62
61
|
// `space list` reaches the client and reports its space.
|
|
63
|
-
await runCommand(canvasElement, 'space list', keyboard);
|
|
62
|
+
await runCommand(canvasElement, 'space list', userEvent.keyboard);
|
|
64
63
|
await waitForTerminal(canvasElement, 'SPACE_READY');
|
|
65
64
|
|
|
66
65
|
// `database query` reaches ECHO through the layer the command provides for itself.
|
|
67
|
-
await runCommand(canvasElement, 'database query', keyboard);
|
|
66
|
+
await runCommand(canvasElement, 'database query', userEvent.keyboard);
|
|
68
67
|
await waitForTerminal(canvasElement, 'org.dxos.type.spaceProperties');
|
|
69
68
|
|
|
70
69
|
// A command from another plugin, whose service arrives via that plugin's contributed layer
|
|
71
70
|
// rather than anything this panel names.
|
|
72
|
-
await runCommand(canvasElement, 'config view', keyboard);
|
|
71
|
+
await runCommand(canvasElement, 'config view', userEvent.keyboard);
|
|
73
72
|
await waitForTerminal(canvasElement, 'runtime');
|
|
74
73
|
},
|
|
75
74
|
};
|
|
@@ -6,10 +6,10 @@ import React from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { Placeholder } from '@dxos/devtools';
|
|
8
8
|
import { useClient } from '@dxos/react-client';
|
|
9
|
-
import { Panel } from '@dxos/react-ui';
|
|
10
9
|
import { Terminal } from '@dxos/react-ui-terminal';
|
|
10
|
+
import * as Panel from '@dxos/react-ui/Panel';
|
|
11
11
|
|
|
12
|
-
import { useCliApp } from './useCliApp';
|
|
12
|
+
import { useCliApp } from './useCliApp.ts';
|
|
13
13
|
|
|
14
14
|
const BOLD = '\x1b[1m';
|
|
15
15
|
const DIM = '\x1b[2m';
|
|
@@ -24,22 +24,22 @@ const BANNER = `${BOLD}DXOS CLI${RESET}\n${DIM}The dx commands, running against
|
|
|
24
24
|
* services are built once per client, so state the handlers hold survives between commands the way
|
|
25
25
|
* it does in a long-lived shell.
|
|
26
26
|
*/
|
|
27
|
-
export const
|
|
27
|
+
export const CliArticle = ({ role }: { role?: string }) => {
|
|
28
28
|
const client = useClient();
|
|
29
29
|
const cli = useCliApp(client);
|
|
30
30
|
|
|
31
31
|
// No ScrollArea: xterm owns its own viewport and scrollback.
|
|
32
32
|
return (
|
|
33
|
-
<Panel.Root>
|
|
34
|
-
<Panel.
|
|
33
|
+
<Panel.Root role={role}>
|
|
34
|
+
<Panel.Body>
|
|
35
35
|
{cli ? (
|
|
36
36
|
<Terminal command={cli.command} layer={cli.layer} name='dx' banner={BANNER} />
|
|
37
37
|
) : (
|
|
38
38
|
<Placeholder label='No commands' />
|
|
39
39
|
)}
|
|
40
|
-
</Panel.
|
|
40
|
+
</Panel.Body>
|
|
41
41
|
</Panel.Root>
|
|
42
42
|
);
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
CliArticle.displayName = 'CliArticle';
|
|
@@ -2,21 +2,22 @@
|
|
|
2
2
|
// Copyright 2026 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import * as Command from 'effect/cli/Command';
|
|
6
|
+
import * as Options from 'effect/cli/Flag';
|
|
5
7
|
import * as Effect from 'effect/Effect';
|
|
6
8
|
import * as Layer from 'effect/Layer';
|
|
7
|
-
import * as Command from 'effect/unstable/cli/Command';
|
|
8
|
-
import * as Options from 'effect/unstable/cli/Flag';
|
|
9
9
|
import { useEffect, useMemo } from 'react';
|
|
10
10
|
|
|
11
11
|
import * as ActivationEvents from '@dxos/app-framework/ActivationEvents';
|
|
12
12
|
import * as Capabilities from '@dxos/app-framework/Capabilities';
|
|
13
13
|
import * as Capability from '@dxos/app-framework/Capability';
|
|
14
|
+
import * as Hooks from '@dxos/app-framework/Hooks';
|
|
14
15
|
import * as Plugin from '@dxos/app-framework/Plugin';
|
|
15
|
-
import
|
|
16
|
+
import * as PluginManagerProvider from '@dxos/app-framework/PluginManagerProvider';
|
|
16
17
|
import { CommandConfig, type CommandServices } from '@dxos/cli-util';
|
|
17
|
-
import { type Client,
|
|
18
|
+
import { type Client, fromClient, fromConfig } from '@dxos/client';
|
|
18
19
|
import * as Operation from '@dxos/compute/Operation';
|
|
19
|
-
import
|
|
20
|
+
import * as EffectEx from '@dxos/effect/EffectEx';
|
|
20
21
|
|
|
21
22
|
const OPERATIONS_UNAVAILABLE = 'Operations are not available in the devtools terminal.';
|
|
22
23
|
|
|
@@ -46,9 +47,9 @@ const operationLayer = Layer.succeed(Operation.Service, {
|
|
|
46
47
|
* Returns `undefined` until something contributes commands — there is no shell to offer yet.
|
|
47
48
|
*/
|
|
48
49
|
export const useCliApp = (client: Client) => {
|
|
49
|
-
const manager = usePluginManager();
|
|
50
|
-
const commands = useCapabilities(Capabilities.Command);
|
|
51
|
-
const contributed = useCapabilities(Capabilities.Layer);
|
|
50
|
+
const manager = PluginManagerProvider.usePluginManager();
|
|
51
|
+
const commands = Hooks.useCapabilities(Capabilities.Command);
|
|
52
|
+
const contributed = Hooks.useCapabilities(Capabilities.Layer);
|
|
52
53
|
|
|
53
54
|
useEffect(() => {
|
|
54
55
|
EffectEx.runDetached(manager.activate(ActivationEvents.CommandsRequested));
|
|
@@ -66,8 +67,12 @@ export const useCliApp = (client: Client) => {
|
|
|
66
67
|
// Shared so they parse anywhere in the line — a plain command config is only read when `dx`
|
|
67
68
|
// itself is the leaf.
|
|
68
69
|
Command.withSharedFlags({
|
|
69
|
-
json: Options.
|
|
70
|
-
verbose: Options.
|
|
70
|
+
json: Options.Boolean('json').pipe(Options.withDefault(false), Options.withDescription('JSON output.')),
|
|
71
|
+
verbose: Options.Boolean('verbose').pipe(
|
|
72
|
+
Options.withDefault(false),
|
|
73
|
+
Options.withAlias('v'),
|
|
74
|
+
Options.withDescription('Verbose output.'),
|
|
75
|
+
),
|
|
71
76
|
}),
|
|
72
77
|
Command.withSubcommands([first, ...rest]),
|
|
73
78
|
// After the subcommands so the config reaches them: `provide` wraps the handler it is applied
|
|
@@ -86,8 +91,8 @@ export const useCliApp = (client: Client) => {
|
|
|
86
91
|
// Annotated so a service this host owes is a build error naming it, rather than a
|
|
87
92
|
// "Service not found" the first time someone runs the command that needed it.
|
|
88
93
|
const hostLayer: Layer.Layer<CommandServices, never, never> = Layer.mergeAll(
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
fromClient(client),
|
|
95
|
+
fromConfig(client.config),
|
|
91
96
|
Layer.succeed(Plugin.Service, manager),
|
|
92
97
|
Layer.succeed(Capability.Service, manager.capabilities),
|
|
93
98
|
operationLayer,
|
|
@@ -6,20 +6,21 @@ import React from 'react';
|
|
|
6
6
|
|
|
7
7
|
import * as AppGraph from '@dxos/app-graph/AppGraph';
|
|
8
8
|
import { Tree } from '@dxos/devtools';
|
|
9
|
-
import
|
|
9
|
+
import * as Panel from '@dxos/react-ui/Panel';
|
|
10
|
+
import * as ScrollArea from '@dxos/react-ui/ScrollArea';
|
|
10
11
|
|
|
11
|
-
export type DebugGraphProps = { graph: AppGraph.Graph; root: string };
|
|
12
|
+
export type DebugGraphProps = { role?: string; graph: AppGraph.Graph; root: string };
|
|
12
13
|
|
|
13
|
-
export const DebugGraph = ({ graph, root }: DebugGraphProps) => {
|
|
14
|
+
export const DebugGraph = ({ role, graph, root }: DebugGraphProps) => {
|
|
14
15
|
return (
|
|
15
|
-
<Panel.Root>
|
|
16
|
-
<Panel.
|
|
16
|
+
<Panel.Root role={role}>
|
|
17
|
+
<Panel.Body asChild>
|
|
17
18
|
<ScrollArea.Root orientation='all'>
|
|
18
19
|
<ScrollArea.Viewport>
|
|
19
20
|
<Tree data={AppGraph.toJSON(graph, root)} />
|
|
20
21
|
</ScrollArea.Viewport>
|
|
21
22
|
</ScrollArea.Root>
|
|
22
|
-
</Panel.
|
|
23
|
+
</Panel.Body>
|
|
23
24
|
</Panel.Root>
|
|
24
25
|
);
|
|
25
26
|
};
|
|
@@ -2,49 +2,89 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, { useCallback, useMemo } from 'react';
|
|
5
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
6
6
|
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import { StatsPanel, useStats } from '@dxos/devtools';
|
|
7
|
+
import * as Surface from '@dxos/app-framework/Surface';
|
|
8
|
+
import * as AppSurface from '@dxos/app-toolkit/AppSurface';
|
|
9
|
+
import { StatsPanel, type SurfaceProfilerStats, useStats } from '@dxos/devtools';
|
|
10
10
|
|
|
11
|
+
import { type DevtoolsCardData, isDebugSurface } from '../../capabilities/DevtoolsCards.tsx';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The stats stack: one poll of the client's stats, fanned out to every card contributed to the role.
|
|
15
|
+
* The profiler and dispatch metrics are sampled on each refresh rather than subscribed to: this
|
|
16
|
+
* container is itself a profiled surface, so a live subscription would record an entry for every
|
|
17
|
+
* re-render the notification caused and loop at frame rate, flushing every other surface out of the
|
|
18
|
+
* profiler's window — which is what made the Surfaces card's count wander with no interaction.
|
|
19
|
+
*/
|
|
11
20
|
export const DevtoolsOverviewContainer = () => {
|
|
12
21
|
const [stats, refreshStats] = useStats();
|
|
13
|
-
const
|
|
14
|
-
const surfaceMetrics = Surface.useMetrics();
|
|
22
|
+
const getProfilerStats = Surface.useProfilerSnapshot();
|
|
15
23
|
const clearSurfaceProfiler = Surface.useProfilerClear();
|
|
24
|
+
// Resampled as surfaces mount and unmount: with a restored layout this container mounts at boot,
|
|
25
|
+
// before most of the app, so a single sample on mount would list only what existed then.
|
|
26
|
+
const mounted = Surface.useMounted();
|
|
27
|
+
const [surfaceProfilerStats, setSurfaceProfilerStats] = useState<SurfaceProfilerStats[]>([]);
|
|
28
|
+
|
|
29
|
+
// One row per surface mounted right now (the profiler records renders, not mounts), with its
|
|
30
|
+
// cumulative render timings and dispatch metrics joined on `surface/<id>/<role>` where they exist.
|
|
31
|
+
// The debug tooling's own surfaces are left out so the panel does not measure itself.
|
|
32
|
+
const sampleProfiler = useCallback(() => {
|
|
33
|
+
const timings = new Map(getProfilerStats().map((stat) => [stat.id, stat]));
|
|
34
|
+
const metrics = new Map(Surface.getMetrics().map((metric) => [metric.id, metric]));
|
|
35
|
+
setSurfaceProfilerStats(
|
|
36
|
+
mounted
|
|
37
|
+
.filter((surface) => !isDebugSurface(surface))
|
|
38
|
+
.map(({ id, role }) => {
|
|
39
|
+
// Mirrors the profiler's own id, which prints an anonymous surface as `undefined`.
|
|
40
|
+
const profilerId = `surface/${id}/${role}`;
|
|
41
|
+
const timing = timings.get(profilerId) ?? {
|
|
42
|
+
id: profilerId,
|
|
43
|
+
mountCount: 0,
|
|
44
|
+
updateCount: 0,
|
|
45
|
+
totalRenders: 0,
|
|
46
|
+
avgActualDuration: 0,
|
|
47
|
+
maxActualDuration: 0,
|
|
48
|
+
avgBaseDuration: 0,
|
|
49
|
+
lastActualDuration: 0,
|
|
50
|
+
lastCommitTime: 0,
|
|
51
|
+
};
|
|
52
|
+
const metric = metrics.get(profilerId);
|
|
53
|
+
return metric
|
|
54
|
+
? {
|
|
55
|
+
...timing,
|
|
56
|
+
candidates: metric.candidates,
|
|
57
|
+
truncated: metric.truncated,
|
|
58
|
+
errors: metric.errors,
|
|
59
|
+
dataUnstable: metric.dataUnstable,
|
|
60
|
+
}
|
|
61
|
+
: timing;
|
|
62
|
+
})
|
|
63
|
+
.sort((a, b) => b.maxActualDuration - a.maxActualDuration || a.id.localeCompare(b.id)),
|
|
64
|
+
);
|
|
65
|
+
}, [getProfilerStats, mounted]);
|
|
66
|
+
useEffect(sampleProfiler, [sampleProfiler]);
|
|
67
|
+
|
|
68
|
+
const handleRefresh = useCallback(() => {
|
|
69
|
+
refreshStats();
|
|
70
|
+
sampleProfiler();
|
|
71
|
+
}, [refreshStats, sampleProfiler]);
|
|
16
72
|
|
|
17
|
-
// The
|
|
73
|
+
// The profiler card joins profiler stats with dispatch metrics, so reset must clear both.
|
|
18
74
|
const handleClearSurfaceProfiler = useCallback(() => {
|
|
19
75
|
clearSurfaceProfiler?.();
|
|
20
76
|
Surface.clearMetrics();
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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]);
|
|
77
|
+
sampleProfiler();
|
|
78
|
+
}, [clearSurfaceProfiler, sampleProfiler]);
|
|
79
|
+
|
|
80
|
+
const data = useMemo<DevtoolsCardData>(
|
|
81
|
+
() => ({ stats, surfaceProfilerStats, onClearSurfaceProfiler: handleClearSurfaceProfiler }),
|
|
82
|
+
[stats, surfaceProfilerStats, handleClearSurfaceProfiler],
|
|
83
|
+
);
|
|
39
84
|
|
|
40
85
|
return (
|
|
41
|
-
<StatsPanel
|
|
42
|
-
|
|
43
|
-
surfaceProfilerStats={enrichedStats}
|
|
44
|
-
onRefresh={refreshStats}
|
|
45
|
-
onClearSurfaceProfiler={handleClearSurfaceProfiler}
|
|
46
|
-
>
|
|
47
|
-
<Surface.Surface type={AppSurface.DevtoolsOverview} />
|
|
86
|
+
<StatsPanel onRefresh={handleRefresh}>
|
|
87
|
+
<Surface.Surface type={AppSurface.DevtoolsOverview} data={data} />
|
|
48
88
|
</StatsPanel>
|
|
49
89
|
);
|
|
50
90
|
};
|
|
@@ -9,11 +9,11 @@ import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
|
9
9
|
|
|
10
10
|
import { translations } from '#translations';
|
|
11
11
|
|
|
12
|
-
import { GithubComponent } from './GithubComponent';
|
|
12
|
+
import { GithubComponent } from './GithubComponent.tsx';
|
|
13
13
|
|
|
14
14
|
const DefaultStory = () => (
|
|
15
15
|
<GithubComponent.Root>
|
|
16
|
-
<div className='grid grid-rows-[auto_minmax(0,1fr)_auto] overflow-hidden
|
|
16
|
+
<div className='grid grid-rows-[auto_minmax(0,1fr)_auto] overflow-hidden dx-fill'>
|
|
17
17
|
<GithubComponent.Header />
|
|
18
18
|
<GithubComponent.Content />
|
|
19
19
|
<GithubComponent.StatusBar />
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { type ReactNode, createContext, useContext, useEffect, useState } from 'react';
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import * as Button from '@dxos/react-ui/Button';
|
|
8
|
+
import * as Hooks from '@dxos/react-ui/Hooks';
|
|
9
|
+
import * as Layout from '@dxos/react-ui/Layout';
|
|
10
|
+
import * as ScrollArea from '@dxos/react-ui/ScrollArea';
|
|
8
11
|
|
|
9
12
|
import { meta } from '#meta';
|
|
10
13
|
|
|
@@ -90,10 +93,10 @@ const Root = ({ repo = DEFAULT_REPO, limit = DEFAULT_LIMIT, children }: GithubCo
|
|
|
90
93
|
};
|
|
91
94
|
|
|
92
95
|
const Header = () => {
|
|
93
|
-
const { t } = useTranslation(meta.profile.key);
|
|
96
|
+
const { t } = Hooks.useTranslation(meta.profile.key);
|
|
94
97
|
const { repo, pulls, unavailable } = useComponentContext();
|
|
95
98
|
return (
|
|
96
|
-
<header className='flex items-center justify-between gap-1 px-4 py-3 dx-modal-surface border-b border-
|
|
99
|
+
<header className='flex items-center justify-between gap-1 px-4 py-3 dx-modal-surface border-b border-separator-subtle'>
|
|
97
100
|
<a
|
|
98
101
|
href={`https://github.com/${repo}`}
|
|
99
102
|
target='_blank'
|
|
@@ -102,7 +105,7 @@ const Header = () => {
|
|
|
102
105
|
>
|
|
103
106
|
@{repo}
|
|
104
107
|
</a>
|
|
105
|
-
<div className='text-xs text-
|
|
108
|
+
<div className='text-xs text-fg-muted'>
|
|
106
109
|
{unavailable
|
|
107
110
|
? t('github-unavailable.message')
|
|
108
111
|
: pulls.length > 0
|
|
@@ -136,18 +139,18 @@ const formatRelative = (iso: string): string => {
|
|
|
136
139
|
|
|
137
140
|
const PullRow = ({ pull }: { pull: GithubPullRequest }) => (
|
|
138
141
|
<li>
|
|
139
|
-
<Flex asChild align='start' gap='sm' classNames='px-trim-sm py-trim-xs rounded-sm hover:bg-hover-surface'>
|
|
142
|
+
<Layout.Flex asChild align='start' gap='sm' classNames='px-trim-sm py-trim-xs rounded-sm hover:bg-hover-surface'>
|
|
140
143
|
<a href={pull.html_url} target='_blank' rel='noopener noreferrer'>
|
|
141
144
|
<img src={pull.user.avatar_url} alt='' className='size-6 rounded-full shrink-0' />
|
|
142
|
-
<Flex column classNames='min-w-0 flex-1'>
|
|
145
|
+
<Layout.Flex column classNames='min-w-0 flex-1'>
|
|
143
146
|
{/* `leading-6` gives the title the avatar's line box, so the two align on their own. */}
|
|
144
147
|
<span className='text-sm leading-6 truncate'>{pull.title}</span>
|
|
145
|
-
<span className='text-xs text-
|
|
148
|
+
<span className='text-xs text-fg-muted truncate'>
|
|
146
149
|
#{pull.number} · {pull.user.login} · {pull.merged_at ? formatRelative(pull.merged_at) : ''}
|
|
147
150
|
</span>
|
|
148
|
-
</Flex>
|
|
151
|
+
</Layout.Flex>
|
|
149
152
|
</a>
|
|
150
|
-
</Flex>
|
|
153
|
+
</Layout.Flex>
|
|
151
154
|
</li>
|
|
152
155
|
);
|
|
153
156
|
|
|
@@ -167,10 +170,10 @@ const Content = () => {
|
|
|
167
170
|
};
|
|
168
171
|
|
|
169
172
|
const StatusBar = () => {
|
|
170
|
-
const { t } = useTranslation(meta.profile.key);
|
|
173
|
+
const { t } = Hooks.useTranslation(meta.profile.key);
|
|
171
174
|
const { repo } = useComponentContext();
|
|
172
175
|
return (
|
|
173
|
-
<
|
|
176
|
+
<Button.Root
|
|
174
177
|
icon='ph--github-logo--regular'
|
|
175
178
|
label={t('view-on-github.button')}
|
|
176
179
|
variant='primary'
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
|
-
import { GithubComponent } from './GithubComponent';
|
|
7
|
+
import { GithubComponent } from './GithubComponent.tsx';
|
|
8
8
|
|
|
9
9
|
export const GithubPanel = () => (
|
|
10
10
|
<GithubComponent.Root>
|
|
11
|
-
<div className='
|
|
11
|
+
<div className='dx-fill grid grid-rows-[auto_minmax(0,1fr)_auto] overflow-hidden'>
|
|
12
12
|
<GithubComponent.Header />
|
|
13
13
|
<GithubComponent.Content />
|
|
14
14
|
<GithubComponent.StatusBar />
|
package/src/containers/{RegistryPanel/RegistryPanel.tsx → RegistryArticle/RegistryArticle.tsx}
RENAMED
|
@@ -8,8 +8,9 @@ import * as Operation from '@dxos/compute/Operation';
|
|
|
8
8
|
import { JsonView, Placeholder, Searchbar } from '@dxos/devtools';
|
|
9
9
|
import { Entity, Format, Obj, Type } from '@dxos/echo';
|
|
10
10
|
import { useClient } from '@dxos/react-client';
|
|
11
|
-
import { Panel, Toolbar } from '@dxos/react-ui';
|
|
12
11
|
import { DynamicTable, type TableFeatures } from '@dxos/react-ui-table';
|
|
12
|
+
import * as Panel from '@dxos/react-ui/Panel';
|
|
13
|
+
import * as Toolbar from '@dxos/react-ui/Toolbar';
|
|
13
14
|
import { mx } from '@dxos/ui-theme';
|
|
14
15
|
|
|
15
16
|
type RegistryRow = {
|
|
@@ -84,7 +85,7 @@ const toDetailJson = (entity: Entity.Unknown): object => {
|
|
|
84
85
|
return { id: getEntityId(entity), typename: Entity.getTypename(entity) };
|
|
85
86
|
};
|
|
86
87
|
|
|
87
|
-
export const
|
|
88
|
+
export const RegistryArticle = ({ role }: { role?: string }) => {
|
|
88
89
|
const client = useClient();
|
|
89
90
|
const [entities, setEntities] = useState<Entity.Unknown[]>([]);
|
|
90
91
|
const [filter, setFilter] = useState('');
|
|
@@ -130,24 +131,24 @@ export const RegistryPanel = () => {
|
|
|
130
131
|
const features: Partial<TableFeatures> = useMemo(() => ({ selection: { enabled: true, mode: 'single' } }), []);
|
|
131
132
|
|
|
132
133
|
return (
|
|
133
|
-
<Panel.Root>
|
|
134
|
-
<Panel.
|
|
134
|
+
<Panel.Root role={role}>
|
|
135
|
+
<Panel.Header>
|
|
135
136
|
<Toolbar.Root>
|
|
136
137
|
<Searchbar placeholder='Filter...' onChange={setFilter} />
|
|
137
138
|
</Toolbar.Root>
|
|
138
|
-
</Panel.
|
|
139
|
-
<Panel.
|
|
139
|
+
</Panel.Header>
|
|
140
|
+
<Panel.Body>
|
|
140
141
|
<div className={mx('h-full grid grid-cols-[2fr_1fr] overflow-hidden')}>
|
|
141
|
-
<div className={mx('flex flex-col
|
|
142
|
+
<div className={mx('flex flex-col dx-grow overflow-hidden')}>
|
|
142
143
|
<DynamicTable properties={properties} rows={rows} features={features} onRowClick={handleRowClicked} />
|
|
143
144
|
</div>
|
|
144
|
-
<div className={mx('
|
|
145
|
+
<div className={mx('h-full overflow-auto border-s border-separator text-sm')}>
|
|
145
146
|
{detailJson ? <JsonView data={detailJson} /> : <Placeholder label='Details' />}
|
|
146
147
|
</div>
|
|
147
148
|
</div>
|
|
148
|
-
</Panel.
|
|
149
|
+
</Panel.Body>
|
|
149
150
|
</Panel.Root>
|
|
150
151
|
);
|
|
151
152
|
};
|
|
152
153
|
|
|
153
|
-
|
|
154
|
+
RegistryArticle.displayName = 'RegistryArticle';
|
|
@@ -7,13 +7,20 @@ import React from 'react';
|
|
|
7
7
|
import { EdgeServiceName } from '@dxos/config';
|
|
8
8
|
import { useEdgeServiceEndpoint } from '@dxos/react-client';
|
|
9
9
|
import { ToolsExplorer } from '@dxos/react-ui-introspect';
|
|
10
|
+
import * as Panel from '@dxos/react-ui/Panel';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Binds the tools explorer to the introspect endpoint from config; the explorer renders its
|
|
13
|
-
* unconfigured state when `runtime.services.
|
|
14
|
+
* unconfigured state when neither `runtime.services.edge.url` nor an `introspect` override is set.
|
|
14
15
|
*/
|
|
15
|
-
export const ToolsExplorerContainer = () => {
|
|
16
|
-
return
|
|
16
|
+
export const ToolsExplorerContainer = ({ role }: { role?: string }) => {
|
|
17
|
+
return (
|
|
18
|
+
<Panel.Root role={role}>
|
|
19
|
+
<Panel.Body>
|
|
20
|
+
<ToolsExplorer serverUrl={useEdgeServiceEndpoint(EdgeServiceName.Introspect)} />
|
|
21
|
+
</Panel.Body>
|
|
22
|
+
</Panel.Root>
|
|
23
|
+
);
|
|
17
24
|
};
|
|
18
25
|
|
|
19
26
|
ToolsExplorerContainer.displayName = 'ToolsExplorerContainer';
|
package/src/containers/index.ts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
import { type ComponentType, lazy } from 'react';
|
|
6
6
|
|
|
7
|
-
export const
|
|
8
|
-
export const DebugGraph: ComponentType<any> = lazy(() => import('./DebugGraph'));
|
|
9
|
-
export const DevtoolsOverviewContainer: ComponentType<any> = lazy(() => import('./DevtoolsOverviewContainer'));
|
|
10
|
-
export const GithubPanel: ComponentType<any> = lazy(() => import('./GithubPanel'));
|
|
11
|
-
export const
|
|
12
|
-
export const ToolsExplorerContainer: ComponentType<any> = lazy(() => import('./ToolsExplorerContainer'));
|
|
7
|
+
export const CliArticle: ComponentType<any> = lazy(() => import('./CliArticle/index.ts'));
|
|
8
|
+
export const DebugGraph: ComponentType<any> = lazy(() => import('./DebugGraph/index.ts'));
|
|
9
|
+
export const DevtoolsOverviewContainer: ComponentType<any> = lazy(() => import('./DevtoolsOverviewContainer/index.ts'));
|
|
10
|
+
export const GithubPanel: ComponentType<any> = lazy(() => import('./GithubPanel/index.ts'));
|
|
11
|
+
export const RegistryArticle: ComponentType<any> = lazy(() => import('./RegistryArticle/index.ts'));
|
|
12
|
+
export const ToolsExplorerContainer: ComponentType<any> = lazy(() => import('./ToolsExplorerContainer/index.ts'));
|
package/src/index.ts
CHANGED
package/src/meta.ts
CHANGED
package/src/plugin.test.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { describe, test } from 'vitest';
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import * as Harness from '@dxos/plugin-testing/Harness';
|
|
8
8
|
|
|
9
9
|
import { meta } from '#meta';
|
|
10
10
|
import { DevtoolsPlugin } from '#plugin';
|
|
@@ -15,7 +15,7 @@ describe('DevtoolsPlugin', () => {
|
|
|
15
15
|
// Boot imports the app-graph-builder body (the harness fires the plugin's start event), which
|
|
16
16
|
// can exceed the default 15s under vite-node transform load.
|
|
17
17
|
test('modules activate on the expected events', { timeout: 60_000 }, async ({ expect }) => {
|
|
18
|
-
await using harness = await createComposerTestApp({
|
|
18
|
+
await using harness = await Harness.createComposerTestApp({
|
|
19
19
|
plugins: [DevtoolsPlugin()],
|
|
20
20
|
});
|
|
21
21
|
|
package/src/translations.ts
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type Resource } from '@dxos/react-ui';
|
|
6
5
|
import { translations as introspectTranslations } from '@dxos/react-ui-introspect/translations';
|
|
6
|
+
import { translations as tableTranslations } from '@dxos/react-ui-table/translations';
|
|
7
|
+
import type * as Theme from '@dxos/react-ui/Theme';
|
|
7
8
|
|
|
8
9
|
import { meta } from '#meta';
|
|
9
10
|
|
|
10
11
|
export const translations = [
|
|
11
12
|
...introspectTranslations,
|
|
13
|
+
// The panels' tables (credentials, feeds, …) read their column menus from react-ui-table.
|
|
14
|
+
...tableTranslations,
|
|
12
15
|
{
|
|
13
16
|
'en-US': {
|
|
14
17
|
[meta.profile.key]: {
|
|
@@ -25,7 +28,7 @@ export const translations = [
|
|
|
25
28
|
'config.label': 'Config',
|
|
26
29
|
'storage.label': 'Storage',
|
|
27
30
|
'sqlite.label': 'SQLite',
|
|
28
|
-
'logging.label': '
|
|
31
|
+
'logging.label': 'Logs',
|
|
29
32
|
'diagnostics.label': 'Diagnostics',
|
|
30
33
|
'tracing.label': 'Tracing',
|
|
31
34
|
'halo.label': 'HALO',
|
|
@@ -38,6 +41,7 @@ export const translations = [
|
|
|
38
41
|
'space.label': 'Space',
|
|
39
42
|
'feeds.label': 'Feeds',
|
|
40
43
|
'objects.label': 'Objects',
|
|
44
|
+
'queries.label': 'Queries',
|
|
41
45
|
'schema.label': 'Schema',
|
|
42
46
|
'registry.label': 'Registry',
|
|
43
47
|
'automerge.label': 'Automerge',
|
|
@@ -64,4 +68,4 @@ export const translations = [
|
|
|
64
68
|
},
|
|
65
69
|
},
|
|
66
70
|
},
|
|
67
|
-
] as const satisfies Resource[];
|
|
71
|
+
] as const satisfies Theme.Resource[];
|