what-devtools 0.5.3 → 0.5.5
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/README.md +90 -0
- package/package.json +5 -5
- package/src/DevPanel.jsx +37 -37
- package/src/index.js +209 -26
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# what-devtools
|
|
2
|
+
|
|
3
|
+
Development tools for [What Framework](https://whatfw.com). Provides runtime instrumentation to inspect signals, effects, and components. Exposes a `window.__WHAT_DEVTOOLS__` global for console-based debugging and a subscribable event system for custom tooling.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install what-devtools --save-dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
Call `installDevTools()` once at app startup:
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import { installDevTools } from 'what-devtools';
|
|
17
|
+
|
|
18
|
+
installDevTools();
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then inspect your app in the browser console:
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
__WHAT_DEVTOOLS__.signals // All live signals with names and values
|
|
25
|
+
__WHAT_DEVTOOLS__.effects // All active effects
|
|
26
|
+
__WHAT_DEVTOOLS__.components // All mounted components
|
|
27
|
+
__WHAT_DEVTOOLS__.getSnapshot() // Full state snapshot
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Event Subscription
|
|
31
|
+
|
|
32
|
+
Subscribe to real-time devtools events for custom tooling or a UI panel:
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
import { subscribe } from 'what-devtools';
|
|
36
|
+
|
|
37
|
+
const unsub = subscribe((event, data) => {
|
|
38
|
+
console.log(event, data);
|
|
39
|
+
// Events:
|
|
40
|
+
// 'signal:created' { id, name, ref, createdAt }
|
|
41
|
+
// 'signal:updated' { id, name, value }
|
|
42
|
+
// 'signal:disposed' { id }
|
|
43
|
+
// 'effect:created' { id, name, createdAt }
|
|
44
|
+
// 'effect:disposed' { id }
|
|
45
|
+
// 'component:mounted' { id, name, element, mountedAt }
|
|
46
|
+
// 'component:unmounted' { id }
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## DevPanel Component
|
|
51
|
+
|
|
52
|
+
A built-in panel component for visual debugging (import separately):
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
import DevPanel from 'what-devtools/panel';
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## API
|
|
59
|
+
|
|
60
|
+
| Export | Description |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `installDevTools(core?)` | Initialize devtools and wire into what-core's hooks |
|
|
63
|
+
| `subscribe(fn)` | Subscribe to devtools events. Returns unsubscribe function |
|
|
64
|
+
| `getSnapshot()` | Get a snapshot of all signals, effects, and components |
|
|
65
|
+
| `registerSignal(sig, name?)` | Manually register a signal |
|
|
66
|
+
| `notifySignalUpdate(sig)` | Notify devtools of a signal value change |
|
|
67
|
+
| `unregisterSignal(sig)` | Unregister a signal |
|
|
68
|
+
| `registerEffect(e, name?)` | Manually register an effect |
|
|
69
|
+
| `unregisterEffect(e)` | Unregister an effect |
|
|
70
|
+
| `registerComponent(name, element)` | Register a component mount |
|
|
71
|
+
| `unregisterComponent(id)` | Unregister a component |
|
|
72
|
+
| `signals` | Map of all tracked signals |
|
|
73
|
+
| `effects` | Map of all tracked effects |
|
|
74
|
+
| `components` | Map of all tracked components |
|
|
75
|
+
|
|
76
|
+
## Sub-path Exports
|
|
77
|
+
|
|
78
|
+
| Path | Contents |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `what-devtools` | Instrumentation API |
|
|
81
|
+
| `what-devtools/panel` | DevPanel UI component |
|
|
82
|
+
|
|
83
|
+
## Links
|
|
84
|
+
|
|
85
|
+
- [Documentation](https://whatfw.com)
|
|
86
|
+
- [GitHub](https://github.com/CelsianJs/whatfw)
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "what-devtools",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "Dev tools for What Framework — signal inspector, component tree, effect graph",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"what-core": "^0.5.3"
|
|
24
24
|
},
|
|
25
|
-
"author": "",
|
|
25
|
+
"author": "ZVN DEV (https://zvndev.com)",
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"repository": {
|
|
28
28
|
"type": "git",
|
|
29
|
-
"url": "https://github.com/
|
|
29
|
+
"url": "https://github.com/CelsianJs/whatfw"
|
|
30
30
|
},
|
|
31
31
|
"bugs": {
|
|
32
|
-
"url": "https://github.com/
|
|
32
|
+
"url": "https://github.com/CelsianJs/whatfw/issues"
|
|
33
33
|
},
|
|
34
|
-
"homepage": "https://
|
|
34
|
+
"homepage": "https://whatfw.com"
|
|
35
35
|
}
|
package/src/DevPanel.jsx
CHANGED
|
@@ -39,9 +39,7 @@ export function DevPanel() {
|
|
|
39
39
|
clearInterval(interval);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
const
|
|
43
|
-
? 'position:fixed;bottom:0;right:0;width:360px;max-height:50vh;z-index:99998;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;background:#1a1a2e;color:#e0e0e0;border:1px solid #2a2a4a;border-radius:12px 0 0 0;box-shadow:0 -4px 24px rgba(0,0,0,0.3);display:flex;flex-direction:column;overflow:hidden;'
|
|
44
|
-
: 'display:none;';
|
|
42
|
+
const PANEL_STYLE = 'position:fixed;bottom:0;right:0;width:360px;max-height:50vh;z-index:99998;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;background:#1a1a2e;color:#e0e0e0;border:1px solid #2a2a4a;border-radius:12px 0 0 0;box-shadow:0 -4px 24px rgba(0,0,0,0.3);display:flex;flex-direction:column;overflow:hidden;';
|
|
45
43
|
|
|
46
44
|
const tabStyle = (tab) => () => {
|
|
47
45
|
const isActive = activeTab() === tab;
|
|
@@ -110,44 +108,46 @@ export function DevPanel() {
|
|
|
110
108
|
W
|
|
111
109
|
</button>
|
|
112
110
|
|
|
113
|
-
{/* Panel */}
|
|
114
|
-
|
|
115
|
-
{
|
|
116
|
-
|
|
117
|
-
<div style="display:flex;align-items:center;
|
|
118
|
-
<
|
|
111
|
+
{/* Panel — conditionally rendered */}
|
|
112
|
+
{() => isOpen() ? (
|
|
113
|
+
<div style={PANEL_STYLE}>
|
|
114
|
+
{/* Header */}
|
|
115
|
+
<div style="display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid #2a2a4a;background:#16163a;">
|
|
116
|
+
<div style="display:flex;align-items:center;gap:8px;">
|
|
117
|
+
<span style="font-weight:700;font-size:12px;color:#818cf8;">What DevTools</span>
|
|
118
|
+
</div>
|
|
119
|
+
<button
|
|
120
|
+
onclick={() => isOpen(false)}
|
|
121
|
+
style="background:none;border:none;color:#6a6a8a;cursor:pointer;font-size:14px;"
|
|
122
|
+
>
|
|
123
|
+
x
|
|
124
|
+
</button>
|
|
119
125
|
</div>
|
|
120
|
-
<button
|
|
121
|
-
onclick={() => isOpen(false)}
|
|
122
|
-
style="background:none;border:none;color:#6a6a8a;cursor:pointer;font-size:14px;"
|
|
123
|
-
>
|
|
124
|
-
x
|
|
125
|
-
</button>
|
|
126
|
-
</div>
|
|
127
126
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
127
|
+
{/* Tabs */}
|
|
128
|
+
<div style="display:flex;gap:4px;padding:6px 8px;border-bottom:1px solid #2a2a4a;">
|
|
129
|
+
<button style={tabStyle('signals')} onclick={() => activeTab('signals')}>
|
|
130
|
+
Signals ({() => snapshot().signals.length})
|
|
131
|
+
</button>
|
|
132
|
+
<button style={tabStyle('effects')} onclick={() => activeTab('effects')}>
|
|
133
|
+
Effects ({() => snapshot().effects.length})
|
|
134
|
+
</button>
|
|
135
|
+
<button style={tabStyle('components')} onclick={() => activeTab('components')}>
|
|
136
|
+
Components ({() => snapshot().components.length})
|
|
137
|
+
</button>
|
|
138
|
+
</div>
|
|
140
139
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
140
|
+
{/* Content */}
|
|
141
|
+
<div style="overflow-y:auto;flex:1;">
|
|
142
|
+
{() => {
|
|
143
|
+
const tab = activeTab();
|
|
144
|
+
if (tab === 'signals') return renderSignals();
|
|
145
|
+
if (tab === 'effects') return renderEffects();
|
|
146
|
+
return renderComponents();
|
|
147
|
+
}}
|
|
148
|
+
</div>
|
|
149
149
|
</div>
|
|
150
|
-
|
|
150
|
+
) : null}
|
|
151
151
|
</>
|
|
152
152
|
);
|
|
153
153
|
}
|
package/src/index.js
CHANGED
|
@@ -20,9 +20,16 @@ let effectId = 0;
|
|
|
20
20
|
let componentId = 0;
|
|
21
21
|
|
|
22
22
|
// Registries
|
|
23
|
-
const signals = new Map(); // id → { name,
|
|
24
|
-
const effects = new Map(); // id → { name,
|
|
25
|
-
const components = new Map(); // id → { name,
|
|
23
|
+
const signals = new Map(); // id → { name, ref, createdAt, internal }
|
|
24
|
+
const effects = new Map(); // id → { name, createdAt, depSignalIds, runCount, lastRunAt }
|
|
25
|
+
const components = new Map(); // id → { name, element, mountedAt, parentId }
|
|
26
|
+
|
|
27
|
+
// Reverse lookup: subscriber Set → signal ID (O(1) dep resolution)
|
|
28
|
+
const subsToSignalId = new WeakMap();
|
|
29
|
+
|
|
30
|
+
// Error log (capped at 100)
|
|
31
|
+
const errors = [];
|
|
32
|
+
const MAX_ERRORS = 100;
|
|
26
33
|
|
|
27
34
|
// Event listeners for the DevPanel
|
|
28
35
|
const listeners = new Set();
|
|
@@ -33,6 +40,88 @@ function emit(event, data) {
|
|
|
33
40
|
}
|
|
34
41
|
}
|
|
35
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Safely serialize a value for transport (WS, JSON).
|
|
45
|
+
* Handles DOM nodes, functions, circular refs, Maps, Sets, large collections.
|
|
46
|
+
*/
|
|
47
|
+
export function safeSerialize(value, depth = 0, seen) {
|
|
48
|
+
if (depth > 6) return '[max depth]';
|
|
49
|
+
if (value === null || value === undefined) return value;
|
|
50
|
+
|
|
51
|
+
const type = typeof value;
|
|
52
|
+
if (type === 'string' || type === 'number' || type === 'boolean') return value;
|
|
53
|
+
if (type === 'function') return `[Function: ${value.name || 'anonymous'}]`;
|
|
54
|
+
if (type === 'symbol') return `[Symbol: ${value.description || ''}]`;
|
|
55
|
+
if (type === 'bigint') return value.toString() + 'n';
|
|
56
|
+
|
|
57
|
+
// DOM nodes
|
|
58
|
+
if (typeof Node !== 'undefined' && value instanceof Node) {
|
|
59
|
+
const tag = value.nodeName?.toLowerCase() || 'node';
|
|
60
|
+
const id = value.id ? `#${value.id}` : '';
|
|
61
|
+
const cls = value.className ? `.${String(value.className).split(' ')[0]}` : '';
|
|
62
|
+
return `[DOM: <${tag}${id}${cls}>]`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!seen) seen = new Set();
|
|
66
|
+
if (seen.has(value)) return '[Circular]';
|
|
67
|
+
seen.add(value);
|
|
68
|
+
|
|
69
|
+
// Map
|
|
70
|
+
if (value instanceof Map) {
|
|
71
|
+
if (value.size > 50) return `[Map: ${value.size} entries]`;
|
|
72
|
+
const obj = {};
|
|
73
|
+
for (const [k, v] of value) {
|
|
74
|
+
obj[String(k)] = safeSerialize(v, depth + 1, seen);
|
|
75
|
+
}
|
|
76
|
+
return { __type: 'Map', entries: obj };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Set
|
|
80
|
+
if (value instanceof Set) {
|
|
81
|
+
if (value.size > 50) return `[Set: ${value.size} items]`;
|
|
82
|
+
return { __type: 'Set', values: [...value].map(v => safeSerialize(v, depth + 1, seen)) };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Array
|
|
86
|
+
if (Array.isArray(value)) {
|
|
87
|
+
if (value.length > 100) {
|
|
88
|
+
return [...value.slice(0, 100).map(v => safeSerialize(v, depth + 1, seen)), `... (${value.length} total)`];
|
|
89
|
+
}
|
|
90
|
+
return value.map(v => safeSerialize(v, depth + 1, seen));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Error
|
|
94
|
+
if (value instanceof Error) {
|
|
95
|
+
return { __type: 'Error', name: value.name, message: value.message, stack: value.stack };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Date
|
|
99
|
+
if (value instanceof Date) return { __type: 'Date', iso: value.toISOString() };
|
|
100
|
+
|
|
101
|
+
// RegExp
|
|
102
|
+
if (value instanceof RegExp) return value.toString();
|
|
103
|
+
|
|
104
|
+
// Plain object
|
|
105
|
+
if (type === 'object') {
|
|
106
|
+
const keys = Object.keys(value);
|
|
107
|
+
if (keys.length > 100) {
|
|
108
|
+
const obj = {};
|
|
109
|
+
for (const k of keys.slice(0, 100)) {
|
|
110
|
+
obj[k] = safeSerialize(value[k], depth + 1, seen);
|
|
111
|
+
}
|
|
112
|
+
obj['...'] = `(${keys.length} total keys)`;
|
|
113
|
+
return obj;
|
|
114
|
+
}
|
|
115
|
+
const obj = {};
|
|
116
|
+
for (const k of keys) {
|
|
117
|
+
obj[k] = safeSerialize(value[k], depth + 1, seen);
|
|
118
|
+
}
|
|
119
|
+
return obj;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return String(value);
|
|
123
|
+
}
|
|
124
|
+
|
|
36
125
|
/**
|
|
37
126
|
* Register a signal with the devtools.
|
|
38
127
|
* Called from reactive.js __DEV__ hooks.
|
|
@@ -42,12 +131,15 @@ export function registerSignal(sig, name) {
|
|
|
42
131
|
const id = ++signalId;
|
|
43
132
|
const entry = {
|
|
44
133
|
id,
|
|
45
|
-
name: name || `signal_${id}`,
|
|
134
|
+
name: sig._debugName || name || `signal_${id}`,
|
|
46
135
|
ref: sig,
|
|
47
136
|
createdAt: Date.now(),
|
|
137
|
+
internal: false,
|
|
48
138
|
};
|
|
49
139
|
signals.set(id, entry);
|
|
50
140
|
sig._devId = id;
|
|
141
|
+
// Reverse lookup for O(1) effect dep resolution
|
|
142
|
+
if (sig._subs) subsToSignalId.set(sig._subs, id);
|
|
51
143
|
emit('signal:created', entry);
|
|
52
144
|
return id;
|
|
53
145
|
}
|
|
@@ -86,6 +178,9 @@ export function registerEffect(e, name) {
|
|
|
86
178
|
id,
|
|
87
179
|
name: name || e.fn?.name || `effect_${id}`,
|
|
88
180
|
createdAt: Date.now(),
|
|
181
|
+
depSignalIds: [],
|
|
182
|
+
runCount: 0,
|
|
183
|
+
lastRunAt: null,
|
|
89
184
|
};
|
|
90
185
|
effects.set(id, entry);
|
|
91
186
|
e._devId = id;
|
|
@@ -93,6 +188,30 @@ export function registerEffect(e, name) {
|
|
|
93
188
|
return id;
|
|
94
189
|
}
|
|
95
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Track effect dependencies and run count after an effect runs.
|
|
193
|
+
*/
|
|
194
|
+
function trackEffectRun(e) {
|
|
195
|
+
const id = e._devId;
|
|
196
|
+
if (id == null) return;
|
|
197
|
+
const entry = effects.get(id);
|
|
198
|
+
if (!entry) return;
|
|
199
|
+
|
|
200
|
+
// Resolve deps via WeakMap reverse lookup — O(m) where m = number of deps
|
|
201
|
+
const depSignalIds = [];
|
|
202
|
+
if (e.deps) {
|
|
203
|
+
for (const subSet of e.deps) {
|
|
204
|
+
const sigId = subsToSignalId.get(subSet);
|
|
205
|
+
if (sigId != null) depSignalIds.push(sigId);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
entry.depSignalIds = depSignalIds;
|
|
210
|
+
entry.runCount = (entry.runCount || 0) + 1;
|
|
211
|
+
entry.lastRunAt = Date.now();
|
|
212
|
+
emit('effect:run', { id, depSignalIds: entry.depSignalIds, runCount: entry.runCount });
|
|
213
|
+
}
|
|
214
|
+
|
|
96
215
|
/**
|
|
97
216
|
* Unregister an effect.
|
|
98
217
|
*/
|
|
@@ -104,16 +223,33 @@ export function unregisterEffect(e) {
|
|
|
104
223
|
emit('effect:disposed', { id });
|
|
105
224
|
}
|
|
106
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Capture a runtime error.
|
|
228
|
+
*/
|
|
229
|
+
function captureError(err, context) {
|
|
230
|
+
const entry = {
|
|
231
|
+
message: err?.message || String(err),
|
|
232
|
+
stack: err?.stack || null,
|
|
233
|
+
type: context?.type || 'unknown',
|
|
234
|
+
effectId: context?.effect?._devId || null,
|
|
235
|
+
timestamp: Date.now(),
|
|
236
|
+
};
|
|
237
|
+
errors.push(entry);
|
|
238
|
+
if (errors.length > MAX_ERRORS) errors.shift();
|
|
239
|
+
emit('error:captured', entry);
|
|
240
|
+
}
|
|
241
|
+
|
|
107
242
|
/**
|
|
108
243
|
* Register a component mount.
|
|
109
244
|
*/
|
|
110
|
-
export function registerComponent(name, element) {
|
|
245
|
+
export function registerComponent(name, element, parentDevId) {
|
|
111
246
|
if (!installed) return;
|
|
112
247
|
const id = ++componentId;
|
|
113
248
|
const entry = {
|
|
114
249
|
id,
|
|
115
250
|
name: name || 'Anonymous',
|
|
116
251
|
element,
|
|
252
|
+
parentId: parentDevId || null,
|
|
117
253
|
mountedAt: Date.now(),
|
|
118
254
|
};
|
|
119
255
|
components.set(id, entry);
|
|
@@ -141,10 +277,15 @@ export function subscribe(fn) {
|
|
|
141
277
|
|
|
142
278
|
/**
|
|
143
279
|
* Get a snapshot of all tracked state.
|
|
280
|
+
* @param {object} [opts] - Options
|
|
281
|
+
* @param {boolean} [opts.includeInternal=false] - Include framework-internal signals
|
|
144
282
|
*/
|
|
145
|
-
export function getSnapshot() {
|
|
283
|
+
export function getSnapshot(opts = {}) {
|
|
284
|
+
const { includeInternal = false } = opts;
|
|
285
|
+
|
|
146
286
|
const signalList = [];
|
|
147
287
|
for (const [id, entry] of signals) {
|
|
288
|
+
if (!includeInternal && entry.internal) continue;
|
|
148
289
|
signalList.push({
|
|
149
290
|
id,
|
|
150
291
|
name: entry.name,
|
|
@@ -154,51 +295,93 @@ export function getSnapshot() {
|
|
|
154
295
|
|
|
155
296
|
const effectList = [];
|
|
156
297
|
for (const [id, entry] of effects) {
|
|
157
|
-
effectList.push({
|
|
298
|
+
effectList.push({
|
|
299
|
+
id,
|
|
300
|
+
name: entry.name,
|
|
301
|
+
depSignalIds: entry.depSignalIds || [],
|
|
302
|
+
runCount: entry.runCount || 0,
|
|
303
|
+
lastRunAt: entry.lastRunAt || null,
|
|
304
|
+
});
|
|
158
305
|
}
|
|
159
306
|
|
|
160
307
|
const componentList = [];
|
|
161
308
|
for (const [id, entry] of components) {
|
|
162
|
-
componentList.push({ id, name: entry.name });
|
|
309
|
+
componentList.push({ id, name: entry.name, parentId: entry.parentId });
|
|
163
310
|
}
|
|
164
311
|
|
|
165
|
-
return {
|
|
312
|
+
return {
|
|
313
|
+
signals: signalList,
|
|
314
|
+
effects: effectList,
|
|
315
|
+
components: componentList,
|
|
316
|
+
errors: errors.slice(),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Get captured errors.
|
|
322
|
+
* @param {object} [opts]
|
|
323
|
+
* @param {number} [opts.since] - Only errors after this timestamp
|
|
324
|
+
*/
|
|
325
|
+
export function getErrors(opts = {}) {
|
|
326
|
+
const { since } = opts;
|
|
327
|
+
if (since) return errors.filter(e => e.timestamp > since);
|
|
328
|
+
return errors.slice();
|
|
166
329
|
}
|
|
167
330
|
|
|
168
331
|
/**
|
|
169
332
|
* Install devtools. Call once at app startup.
|
|
170
333
|
* Wires into what-core's __DEV__ hooks and exposes `window.__WHAT_DEVTOOLS__`.
|
|
334
|
+
*
|
|
335
|
+
* @param {object} [core] - Optional what-core module. If not provided, attempts dynamic import.
|
|
171
336
|
*/
|
|
172
|
-
export function installDevTools() {
|
|
337
|
+
export function installDevTools(core) {
|
|
173
338
|
if (installed) return;
|
|
174
339
|
installed = true;
|
|
175
340
|
|
|
341
|
+
const hooks = {
|
|
342
|
+
onSignalCreate: (sig) => registerSignal(sig),
|
|
343
|
+
onSignalUpdate: (sig) => notifySignalUpdate(sig),
|
|
344
|
+
onEffectCreate: (e) => registerEffect(e),
|
|
345
|
+
onEffectDispose: (e) => unregisterEffect(e),
|
|
346
|
+
onEffectRun: (e) => trackEffectRun(e),
|
|
347
|
+
onError: (err, context) => captureError(err, context),
|
|
348
|
+
onComponentMount: (ctx) => {
|
|
349
|
+
const name = ctx.Component?.displayName || ctx.Component?.name || 'Anonymous';
|
|
350
|
+
const parentDevId = ctx._parentCtx?._devId || null;
|
|
351
|
+
const id = registerComponent(name, ctx._wrapper, parentDevId);
|
|
352
|
+
ctx._devId = id;
|
|
353
|
+
},
|
|
354
|
+
onComponentUnmount: (ctx) => {
|
|
355
|
+
if (ctx._devId != null) unregisterComponent(ctx._devId);
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
|
|
176
359
|
// Wire into what-core's reactive system
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
// what-core not available — devtools still work via manual registration
|
|
189
|
-
});
|
|
190
|
-
} catch {}
|
|
360
|
+
if (core && core.__setDevToolsHooks) {
|
|
361
|
+
core.__setDevToolsHooks(hooks);
|
|
362
|
+
if (typeof window !== 'undefined') window.__WHAT_CORE__ = core;
|
|
363
|
+
} else {
|
|
364
|
+
try {
|
|
365
|
+
import('what-core').then(mod => {
|
|
366
|
+
if (mod.__setDevToolsHooks) mod.__setDevToolsHooks(hooks);
|
|
367
|
+
if (typeof window !== 'undefined') window.__WHAT_CORE__ = mod;
|
|
368
|
+
}).catch(() => {});
|
|
369
|
+
} catch {}
|
|
370
|
+
}
|
|
191
371
|
|
|
192
372
|
if (typeof window !== 'undefined') {
|
|
193
373
|
window.__WHAT_DEVTOOLS__ = {
|
|
194
374
|
get signals() { return getSnapshot().signals; },
|
|
195
375
|
get effects() { return getSnapshot().effects; },
|
|
196
376
|
get components() { return getSnapshot().components; },
|
|
377
|
+
get errors() { return getErrors(); },
|
|
197
378
|
getSnapshot,
|
|
379
|
+
getErrors,
|
|
198
380
|
subscribe,
|
|
199
|
-
|
|
381
|
+
safeSerialize,
|
|
382
|
+
_registries: { signals, effects, components, errors },
|
|
200
383
|
};
|
|
201
384
|
}
|
|
202
385
|
}
|
|
203
386
|
|
|
204
|
-
export { signals, effects, components };
|
|
387
|
+
export { signals, effects, components, errors };
|