@praxisjs/devtools 0.1.1 → 0.2.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 (33) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/index.d.ts +5 -7
  3. package/dist/index.js +1035 -860
  4. package/package.json +8 -7
  5. package/src/decorators/debug.ts +102 -77
  6. package/src/decorators/trace.ts +15 -11
  7. package/src/icons/ellipsis-vertical.tsx +25 -19
  8. package/src/icons/panel-bottom.tsx +24 -18
  9. package/src/icons/panel-left.tsx +24 -18
  10. package/src/icons/panel-right.tsx +24 -18
  11. package/src/icons/panel-top.tsx +24 -18
  12. package/src/icons/x.tsx +24 -18
  13. package/src/plugins/components/components/component-detail.tsx +59 -54
  14. package/src/plugins/components/components/component-row.tsx +36 -33
  15. package/src/plugins/components/components/detail-row.tsx +21 -18
  16. package/src/plugins/components/components/detail-section.tsx +14 -12
  17. package/src/plugins/components/components/status-dot.tsx +20 -11
  18. package/src/plugins/components/components-tab.tsx +66 -61
  19. package/src/plugins/signals/components/signal-detail.tsx +35 -27
  20. package/src/plugins/signals/components/signal-row.tsx +32 -28
  21. package/src/plugins/signals/signals-tab.tsx +92 -79
  22. package/src/plugins/timeline/components/badge.tsx +15 -9
  23. package/src/plugins/timeline/components/timeline-row.tsx +52 -45
  24. package/src/plugins/timeline/timeline-tab.tsx +90 -77
  25. package/src/plugins/types.ts +2 -2
  26. package/src/ui/dev-tools.tsx +45 -39
  27. package/src/ui/panel.tsx +152 -146
  28. package/src/ui/shared/empty-state.tsx +17 -11
  29. package/src/ui/shared/icon-button.tsx +28 -25
  30. package/src/ui/shared/panel-section.tsx +14 -12
  31. package/src/ui/shared/search-input.tsx +19 -15
  32. package/src/ui/shared/side-panel.tsx +16 -13
  33. package/vite.config.ts +3 -9
@@ -1,7 +1,9 @@
1
1
  import { EmptyState } from "@shared/empty-state";
2
2
  import { SearchInput } from "@shared/search-input";
3
3
 
4
- import { signal, onMount, onUnmount } from "@praxisjs/core";
4
+ import { StatefulComponent } from "@praxisjs/core";
5
+ import { Component, State } from "@praxisjs/decorators";
6
+
5
7
 
6
8
  import { SignalDetail } from "./components/signal-detail";
7
9
  import { SignalRow } from "./components/signal-row";
@@ -9,91 +11,102 @@ import { SignalRow } from "./components/signal-row";
9
11
  import type { Registry } from "@core/registry";
10
12
  import type { SignalEntry } from "@core/types";
11
13
 
12
- export function SignalsTab({ registry }: { registry: Registry }) {
13
- const signals = signal<SignalEntry[]>(registry.getSignals());
14
- const search = signal("");
15
- const selectedId = signal<string | null>(null);
14
+ @Component()
15
+ export class SignalsTab extends StatefulComponent {
16
+ @State() signals: SignalEntry[] = [];
17
+ @State() search = "";
18
+ @State() selectedId: string | null = null;
19
+
20
+ private _handlers: Array<() => void> = [];
21
+
22
+ private get registry() {
23
+ return this.props.registry as Registry;
24
+ }
25
+
26
+ onMount() {
27
+ this.signals = this.registry.getSignals();
16
28
 
17
- let handlers: Array<() => void> = [];
18
- onMount(() => {
19
- handlers = [
20
- registry.bus.on("signal:registered", () => {
21
- signals.set(registry.getSignals());
29
+ this._handlers = [
30
+ this.registry.bus.on("signal:registered", () => {
31
+ this.signals = this.registry.getSignals();
22
32
  }),
23
- registry.bus.on("signal:changed", () => {
24
- signals.set(registry.getSignals());
33
+ this.registry.bus.on("signal:changed", () => {
34
+ this.signals = this.registry.getSignals();
25
35
  }),
26
36
  ];
27
- });
28
-
29
- onUnmount(() => {
30
- handlers.forEach((off) => {
31
- off();
32
- });
33
- });
34
-
35
- return (
36
- <div class="flex h-full overflow-hidden">
37
- <div class="flex-1 flex flex-col overflow-hidden min-w-0">
38
- <div class="px-3 py-2 border-b border-border bg-bg shrink-0">
39
- <SearchInput
40
- placeholder="Search signals…"
41
- onInput={(v) => {
42
- search.set(v);
43
- }}
44
- />
45
- </div>
37
+ }
46
38
 
47
- <div class="grid grid-cols-[1.2fr_0.8fr_1fr_auto] items-center px-3 h-7 text-[9px] text-subtle font-bold tracking-[0.12em] uppercase border-b border-border bg-section gap-2 shrink-0">
48
- <span>Signal</span>
49
- <span>Component</span>
50
- <span>Value</span>
51
- <span>Age</span>
52
- </div>
39
+ onUnmount() {
40
+ this._handlers.forEach((off) => { off(); });
41
+ }
53
42
 
54
- <div class="flex-1 overflow-y-auto">
55
- {() => {
56
- const q = search().toLowerCase();
57
- const filtered =
58
- q === ""
59
- ? signals()
60
- : signals().filter(
61
- (s) =>
62
- s.label.toLowerCase().includes(q) ||
63
- s.componentName.toLowerCase().includes(q),
64
- );
65
-
66
- if (filtered.length === 0) {
67
- return (
68
- <EmptyState
69
- message={
70
- signals().length === 0
71
- ? "No signals tracked. Add @Debug() on top of @State() properties."
72
- : "No signals match your search."
73
- }
43
+ render() {
44
+ return (
45
+ <div class="flex h-full overflow-hidden">
46
+ <div class="flex-1 flex flex-col overflow-hidden min-w-0">
47
+ <div class="px-3 py-2 border-b border-border bg-bg shrink-0">
48
+ <SearchInput
49
+ placeholder="Search signals…"
50
+ onInput={(v) => {
51
+ this.search = v;
52
+ }}
53
+ />
54
+ </div>
55
+
56
+ <div class="grid grid-cols-[1.2fr_0.8fr_1fr_auto] items-center px-3 h-7 text-[9px] text-subtle font-bold tracking-[0.12em] uppercase border-b border-border bg-section gap-2 shrink-0">
57
+ <span>Signal</span>
58
+ <span>Component</span>
59
+ <span>Value</span>
60
+ <span>Age</span>
61
+ </div>
62
+
63
+ <div class="flex-1 overflow-y-auto">
64
+ {() => {
65
+ const q = this.search.toLowerCase();
66
+ const filtered =
67
+ q === ""
68
+ ? this.signals
69
+ : this.signals.filter(
70
+ (s) =>
71
+ s.label.toLowerCase().includes(q) ||
72
+ s.componentName.toLowerCase().includes(q),
73
+ );
74
+
75
+ if (filtered.length === 0) {
76
+ return (
77
+ <EmptyState
78
+ message={
79
+ this.signals.length === 0
80
+ ? "No signals tracked. Add @Debug() on top of @State() properties."
81
+ : "No signals match your search."
82
+ }
83
+ />
84
+ );
85
+ }
86
+
87
+ return filtered.map((s) => (
88
+ <SignalRow
89
+ key={s.id}
90
+ entry={s}
91
+ selected={this.selectedId === s.id}
92
+ onClick={() => {
93
+ this.selectedId =
94
+ this.selectedId === s.id ? null : s.id;
95
+ }}
74
96
  />
75
- );
76
- }
77
-
78
- return filtered.map((s) => (
79
- <SignalRow
80
- key={s.id}
81
- entry={s}
82
- selected={selectedId() === s.id}
83
- onClick={() => {
84
- selectedId.update((id) => (id === s.id ? null : s.id));
85
- }}
86
- />
87
- ));
88
- }}
97
+ ));
98
+ }}
99
+ </div>
89
100
  </div>
90
- </div>
91
101
 
92
- {() => {
93
- const id = selectedId();
94
- const entry = id ? (signals().find((s) => s.id === id) ?? null) : null;
95
- return entry ? <SignalDetail entry={entry} /> : null;
96
- }}
97
- </div>
98
- );
102
+ {() => {
103
+ const id = this.selectedId;
104
+ const entry = id
105
+ ? (this.signals.find((s) => s.id === id) ?? null)
106
+ : null;
107
+ return entry ? <SignalDetail entry={entry} /> : null;
108
+ }}
109
+ </div>
110
+ );
111
+ }
99
112
  }
@@ -1,14 +1,20 @@
1
+ import { StatelessComponent } from "@praxisjs/core";
2
+ import { Component } from "@praxisjs/decorators";
3
+
1
4
  import { TYPE_META } from "../constants";
2
5
 
3
6
  import type { TimelineEventType } from "@core/types";
4
7
 
5
- export function Badge({ type }: { type: TimelineEventType }) {
6
- const meta = TYPE_META[type];
7
- return (
8
- <span
9
- class={`text-[9px] px-[6px] py-[2px] rounded font-bold uppercase tracking-[0.07em] shrink-0 ${meta.cls}`}
10
- >
11
- {meta.label}
12
- </span>
13
- );
8
+ @Component()
9
+ export class Badge extends StatelessComponent<{ type: TimelineEventType }> {
10
+ render() {
11
+ const meta = TYPE_META[this.props.type];
12
+ return (
13
+ <span
14
+ class={`text-[9px] px-[6px] py-[2px] rounded font-bold uppercase tracking-[0.07em] shrink-0 ${meta.cls}`}
15
+ >
16
+ {meta.label}
17
+ </span>
18
+ );
19
+ }
14
20
  }
@@ -1,55 +1,62 @@
1
1
  import { time } from "@utils/format-time";
2
2
 
3
- import { signal } from "@praxisjs/core";
3
+ import { StatefulComponent } from "@praxisjs/core";
4
+ import { Component, State } from "@praxisjs/decorators";
5
+
4
6
 
5
7
  import { Badge } from "./badge";
6
8
 
7
9
  import type { TimelineEntry } from "@core/types";
8
10
 
9
- export function TimelineRow({ entry }: { entry: TimelineEntry }) {
10
- const open = signal(false);
11
- const hasData = Object.keys(entry.data).length > 0;
12
-
13
- return (
14
- <div class="border-b border-border">
15
- <div
16
- onClick={() => {
17
- if (hasData) open.update((v) => !v);
18
- }}
19
- class={`flex items-center gap-2 px-3 py-2 transition-colors duration-100 ${
20
- hasData ? "cursor-pointer hover:bg-section" : "cursor-default"
21
- }`}
22
- >
23
- <span class="text-subtle text-[10px] w-10 shrink-0 text-right tabular-nums font-mono">
24
- {time(entry.timestamp)}
25
- </span>
26
- <Badge type={entry.type} />
27
- <span class="flex-1 text-text font-mono text-[11px] truncate">
28
- {entry.label}
29
- </span>
30
- {hasData && (
31
- <span class="text-subtle text-[11px] shrink-0 w-4 text-center">
32
- {() => (open() ? "▾" : "▸")}
11
+ @Component()
12
+ export class TimelineRow extends StatefulComponent {
13
+ @State() open = false;
14
+
15
+ render() {
16
+ const { entry } = this.props as { entry: TimelineEntry };
17
+ const hasData = Object.keys(entry.data).length > 0;
18
+
19
+ return (
20
+ <div class="border-b border-border">
21
+ <div
22
+ onClick={() => {
23
+ if (hasData) this.open = !this.open;
24
+ }}
25
+ class={`flex items-center gap-2 px-3 py-2 transition-colors duration-100 ${
26
+ hasData ? "cursor-pointer hover:bg-section" : "cursor-default"
27
+ }`}
28
+ >
29
+ <span class="text-subtle text-[10px] w-10 shrink-0 text-right tabular-nums font-mono">
30
+ {time(entry.timestamp)}
33
31
  </span>
34
- )}
35
- </div>
32
+ <Badge type={entry.type} />
33
+ <span class="flex-1 text-text font-mono text-[11px] truncate">
34
+ {entry.label}
35
+ </span>
36
+ {hasData && (
37
+ <span class="text-subtle text-[11px] shrink-0 w-4 text-center">
38
+ {() => (this.open ? "▾" : "▸")}
39
+ </span>
40
+ )}
41
+ </div>
36
42
 
37
- {() =>
38
- open() && hasData ? (
39
- <div class="px-3 py-2 pl-[56px] font-mono text-[11px] bg-section border-t border-border">
40
- {Object.entries(entry.data).map(([k, v]) => (
41
- <div key={k} class="flex gap-3 py-[3px]">
42
- <span class="text-accent shrink-0">{k}:</span>
43
- <span class="text-muted truncate">
44
- {typeof v === "object"
45
- ? JSON.stringify(v)
46
- : String(v as unknown)}
47
- </span>
48
- </div>
49
- ))}
50
- </div>
51
- ) : null
52
- }
53
- </div>
54
- );
43
+ {() =>
44
+ this.open && hasData ? (
45
+ <div class="px-3 py-2 pl-[56px] font-mono text-[11px] bg-section border-t border-border">
46
+ {Object.entries(entry.data).map(([k, v]) => (
47
+ <div key={k} class="flex gap-3 py-[3px]">
48
+ <span class="text-accent shrink-0">{k}:</span>
49
+ <span class="text-muted truncate">
50
+ {typeof v === "object"
51
+ ? JSON.stringify(v)
52
+ : String(v as unknown)}
53
+ </span>
54
+ </div>
55
+ ))}
56
+ </div>
57
+ ) : null
58
+ }
59
+ </div>
60
+ );
61
+ }
55
62
  }
@@ -1,6 +1,8 @@
1
1
  import { EmptyState } from "@shared/empty-state";
2
2
 
3
- import { signal, onMount, onUnmount } from "@praxisjs/core";
3
+ import { StatefulComponent } from "@praxisjs/core";
4
+ import { Component, State } from "@praxisjs/decorators";
5
+
4
6
 
5
7
  import { TimelineRow } from "./components/timeline-row";
6
8
  import { FILTERS, type Filter } from "./constants";
@@ -8,94 +10,105 @@ import { FILTERS, type Filter } from "./constants";
8
10
  import type { Registry } from "@core/registry";
9
11
  import type { TimelineEntry } from "@core/types";
10
12
 
11
- export function TimelineTab({ registry }: { registry: Registry }) {
12
- const entries = signal<TimelineEntry[]>(registry.getTimeline());
13
- const filter = signal<Filter>("all");
14
- const paused = signal(false);
13
+ @Component()
14
+ export class TimelineTab extends StatefulComponent {
15
+ @State() entries: TimelineEntry[] = [];
16
+ @State() filter: Filter = "all";
17
+ @State() paused = false;
18
+
19
+ private _handlers: Array<() => void> = [];
20
+
21
+ private get registry() {
22
+ return this.props.registry as Registry;
23
+ }
15
24
 
16
- let handlers: Array<() => void> = [];
17
- onMount(() => {
18
- handlers = [
19
- registry.bus.on("timeline:push", () => {
20
- if (!paused()) entries.set(registry.getTimeline());
25
+ onMount() {
26
+ this.entries = this.registry.getTimeline();
27
+
28
+ this._handlers = [
29
+ this.registry.bus.on("timeline:push", () => {
30
+ if (!this.paused) this.entries = this.registry.getTimeline();
21
31
  }),
22
32
  ];
23
- });
24
-
25
- onUnmount(() => {
26
- handlers.forEach((off) => {
27
- off();
28
- });
29
- });
30
-
31
- return (
32
- <div class="flex flex-col h-full overflow-hidden">
33
- <div class="flex items-center gap-[3px] px-2 py-2 border-b border-border bg-bg shrink-0 flex-wrap">
34
- {FILTERS.map((f) => (
33
+ }
34
+
35
+ onUnmount() {
36
+ this._handlers.forEach((off) => { off(); });
37
+ }
38
+
39
+ render() {
40
+ return (
41
+ <div class="flex flex-col h-full overflow-hidden">
42
+ <div class="flex items-center gap-[3px] px-2 py-2 border-b border-border bg-bg shrink-0 flex-wrap">
43
+ {FILTERS.map((f) => (
44
+ <button
45
+ key={f.value}
46
+ onClick={() => {
47
+ this.filter = f.value;
48
+ }}
49
+ class={() =>
50
+ this.filter === f.value
51
+ ? "text-[11px] px-2 py-[3px] rounded cursor-pointer font-sans bg-soft text-accent font-semibold"
52
+ : "text-[11px] px-2 py-[3px] rounded cursor-pointer font-sans text-muted hover:text-text hover:bg-section transition-colors duration-150"
53
+ }
54
+ >
55
+ {f.label}
56
+ </button>
57
+ ))}
58
+
59
+ <div class="flex-1" />
60
+
35
61
  <button
36
- key={f.value}
37
62
  onClick={() => {
38
- filter.set(f.value);
63
+ if (this.paused) this.entries = this.registry.getTimeline();
64
+ this.paused = !this.paused;
39
65
  }}
40
66
  class={() =>
41
- filter() === f.value
42
- ? "text-[11px] px-2 py-[3px] rounded cursor-pointer font-sans bg-soft text-accent font-semibold"
43
- : "text-[11px] px-2 py-[3px] rounded cursor-pointer font-sans text-muted hover:text-text hover:bg-section transition-colors duration-150"
67
+ `text-[11px] px-2 py-[3px] rounded cursor-pointer font-sans border border-border transition-colors duration-150 ${
68
+ this.paused
69
+ ? "text-warn border-warn"
70
+ : "text-muted hover:text-text"
71
+ }`
44
72
  }
45
73
  >
46
- {f.label}
74
+ {() => (this.paused ? "Resume" : "Pause")}
47
75
  </button>
48
- ))}
49
76
 
50
- <div class="flex-1" />
77
+ <button
78
+ onClick={() => {
79
+ this.entries = [];
80
+ }}
81
+ class="text-[11px] px-2 py-[3px] rounded cursor-pointer font-sans border border-border text-muted hover:text-text transition-colors duration-150"
82
+ >
83
+ Clear
84
+ </button>
85
+ </div>
51
86
 
52
- <button
53
- onClick={() => {
54
- if (paused()) entries.set(registry.getTimeline());
55
- paused.update((v) => !v);
56
- }}
57
- class={() =>
58
- `text-[11px] px-2 py-[3px] rounded cursor-pointer font-sans border border-border transition-colors duration-150 ${
59
- paused() ? "text-warn border-warn" : "text-muted hover:text-text"
60
- }`
61
- }
62
- >
63
- {() => (paused() ? "Resume" : "Pause")}
64
- </button>
65
-
66
- <button
67
- onClick={() => {
68
- entries.set([]);
69
- }}
70
- class="text-[11px] px-2 py-[3px] rounded cursor-pointer font-sans border border-border text-muted hover:text-text transition-colors duration-150"
71
- >
72
- Clear
73
- </button>
74
- </div>
87
+ <div class="flex-1 overflow-y-auto">
88
+ {() => {
89
+ const f = this.filter;
90
+ const filtered =
91
+ f === "all"
92
+ ? this.entries
93
+ : this.entries.filter(
94
+ (e) =>
95
+ e.type === f ||
96
+ (f === "component:mount" &&
97
+ e.type === "component:unmount"),
98
+ );
99
+
100
+ if (filtered.length === 0) {
101
+ return (
102
+ <EmptyState message="No events yet. Interact with your app to see the timeline." />
103
+ );
104
+ }
75
105
 
76
- <div class="flex-1 overflow-y-auto">
77
- {() => {
78
- const f = filter();
79
- const filtered =
80
- f === "all"
81
- ? entries()
82
- : entries().filter(
83
- (e) =>
84
- e.type === f ||
85
- (f === "component:mount" && e.type === "component:unmount"),
86
- );
87
-
88
- if (filtered.length === 0) {
89
- return (
90
- <EmptyState message="No events yet. Interact with your app to see the timeline." />
91
- );
92
- }
93
-
94
- return [...filtered]
95
- .reverse()
96
- .map((e) => <TimelineRow key={e.id} entry={e} />);
97
- }}
106
+ return [...filtered]
107
+ .reverse()
108
+ .map((e) => <TimelineRow key={e.id} entry={e} />);
109
+ }}
110
+ </div>
98
111
  </div>
99
- </div>
100
- );
112
+ );
113
+ }
101
114
  }
@@ -1,4 +1,4 @@
1
- import type { FunctionComponent } from "@praxisjs/shared";
1
+ import type { ComponentElement } from "@praxisjs/shared";
2
2
 
3
3
  import type { Registry } from "@core/registry";
4
4
 
@@ -6,5 +6,5 @@ export interface DevtoolsPlugin {
6
6
  id: string;
7
7
  label: string;
8
8
  setup?: (registry: Registry) => void;
9
- component: FunctionComponent<{ registry: Registry }>;
9
+ component: ComponentElement;
10
10
  }
@@ -6,7 +6,8 @@ import { SignalsPlugin } from "@plugins/signals";
6
6
  import { TimelinePlugin } from "@plugins/timeline";
7
7
  import unoReset from "@unocss/reset/tailwind-v4.css?inline";
8
8
 
9
- import { signal } from "@praxisjs/core";
9
+ import { StatefulComponent } from "@praxisjs/core";
10
+ import { Component, State } from "@praxisjs/decorators";
10
11
  import { render } from "@praxisjs/runtime";
11
12
 
12
13
  import { Panel } from "./panel";
@@ -23,42 +24,47 @@ export interface DevToolsOptions {
23
24
  plugins?: DevtoolsPlugin[];
24
25
  }
25
26
 
26
- function DevToolsApp({
27
- plugins,
28
- registry,
29
- }: {
30
- plugins: DevtoolsPlugin[];
31
- registry: Registry;
32
- }) {
33
- const open = signal(false);
34
-
35
- return (
36
- <div>
37
- {() =>
38
- open() ? (
39
- <Panel
40
- plugins={plugins}
41
- registry={registry}
42
- onClose={() => {
43
- open.set(false);
44
- }}
45
- />
46
- ) : (
47
- <button
48
- onClick={() => {
49
- open.set(true);
50
- }}
51
- class="fixed bottom-5 right-5 z-[2147483647] flex items-center gap-1 pl-[10px] pr-[14px] h-[32px] rounded-xl font-sans font-semibold text-[12px] text-accent bg-header border border-border cursor-pointer select-none shadow-[0_8px_32px_rgba(0,0,0,0.7)] transition-all duration-200 hover:border-accent hover:bg-soft hover:shadow-[0_8px_32px_rgba(56,189,248,0.1)]"
52
- >
53
- <img class="h-[13px] w-[13px]" src={logo} />
54
- <span class="text-[10px] font-bold text-accent tracking-widest uppercase">
55
- devtools
56
- </span>
57
- </button>
58
- )
59
- }
60
- </div>
61
- );
27
+ @Component()
28
+ class DevToolsApp extends StatefulComponent {
29
+ @State() open = false;
30
+
31
+ private get p() {
32
+ return this.props as unknown as {
33
+ plugins: DevtoolsPlugin[];
34
+ registry: Registry;
35
+ };
36
+ }
37
+
38
+ render() {
39
+ const { plugins, registry } = this.p;
40
+ return (
41
+ <div>
42
+ {() =>
43
+ this.open ? (
44
+ <Panel
45
+ plugins={plugins}
46
+ registry={registry}
47
+ onClose={() => {
48
+ this.open = false;
49
+ }}
50
+ />
51
+ ) : (
52
+ <button
53
+ onClick={() => {
54
+ this.open = true;
55
+ }}
56
+ class="fixed bottom-5 right-5 z-[2147483647] flex items-center gap-1 pl-[10px] pr-[14px] h-[32px] rounded-xl font-sans font-semibold text-[12px] text-accent bg-header border border-border cursor-pointer select-none shadow-[0_8px_32px_rgba(0,0,0,0.7)] transition-all duration-200 hover:border-accent hover:bg-soft hover:shadow-[0_8px_32px_rgba(56,189,248,0.1)]"
57
+ >
58
+ <img class="h-[13px] w-[13px]" src={logo} />
59
+ <span class="text-[10px] font-bold text-accent tracking-widest uppercase">
60
+ devtools
61
+ </span>
62
+ </button>
63
+ )
64
+ }
65
+ </div>
66
+ );
67
+ }
62
68
  }
63
69
 
64
70
  export const DevTools = {
@@ -90,7 +96,7 @@ export const DevTools = {
90
96
  shadow.appendChild(container);
91
97
 
92
98
  this.cleanup = render(
93
- <DevToolsApp plugins={this.plugins} registry={Registry.instance} />,
99
+ () => <DevToolsApp plugins={this.plugins} registry={Registry.instance} />,
94
100
  container,
95
101
  );
96
102
  },
@@ -114,7 +120,7 @@ export const DevTools = {
114
120
 
115
121
  this.cleanup?.();
116
122
  this.cleanup = render(
117
- <DevToolsApp plugins={this.plugins} registry={Registry.instance} />,
123
+ () => <DevToolsApp plugins={this.plugins} registry={Registry.instance} />,
118
124
  container,
119
125
  );
120
126
  },