what-devtools 0.10.0 → 0.11.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/package.json +2 -2
- package/src/DevPanel.jsx +29 -9
- package/src/index.js +41 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "what-devtools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Dev tools for What Framework — signal inspector, component tree, effect graph",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"inspector"
|
|
21
21
|
],
|
|
22
22
|
"peerDependencies": {
|
|
23
|
-
"what-core": "^0.
|
|
23
|
+
"what-core": "^0.11.0"
|
|
24
24
|
},
|
|
25
25
|
"author": "ZVN DEV (https://zvndev.com)",
|
|
26
26
|
"license": "MIT",
|
package/src/DevPanel.jsx
CHANGED
|
@@ -20,12 +20,25 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { signal, effect, onCleanup } from 'what-core';
|
|
23
|
-
import { subscribe, getSnapshot, getErrors, installDevTools } from './index.js';
|
|
23
|
+
import { subscribe, getSnapshot, getErrors, installDevTools, _suppressDevtools } from './index.js';
|
|
24
24
|
|
|
25
25
|
export function DevPanel() {
|
|
26
26
|
// Auto-install devtools if not already done
|
|
27
27
|
installDevTools();
|
|
28
28
|
|
|
29
|
+
// The panel's ENTIRE body runs with devtools registration suppressed —
|
|
30
|
+
// the panel must not appear in its own signal/effect lists. More
|
|
31
|
+
// importantly: if panel-internal effects registered, every panel re-render
|
|
32
|
+
// would emit effect:created events, the subscribe() callback below would
|
|
33
|
+
// write `snapshot`, that write would re-render the panel, creating more
|
|
34
|
+
// effects → an unbounded feedback loop that crashes the page.
|
|
35
|
+
// The two bindings that (re)create DOM subtrees after mount (the isOpen
|
|
36
|
+
// panel toggle and the tab-content switch) carry their own _suppressDevtools
|
|
37
|
+
// wrappers, because their re-runs happen outside this bracket.
|
|
38
|
+
return _suppressDevtools(() => DevPanelBody());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function DevPanelBody() {
|
|
29
42
|
const isOpen = signal(false);
|
|
30
43
|
const activeTab = signal('overview');
|
|
31
44
|
const snapshot = signal(getSnapshot());
|
|
@@ -291,7 +304,11 @@ export function DevPanel() {
|
|
|
291
304
|
};
|
|
292
305
|
|
|
293
306
|
return (
|
|
294
|
-
|
|
307
|
+
// display:contents wrapper instead of a fragment — the babel plugin
|
|
308
|
+
// currently miscompiles top-level fragments whose element children carry
|
|
309
|
+
// event handlers (it references _el$N bindings it never emits). The
|
|
310
|
+
// wrapper is layout-neutral; both children are position:fixed anyway.
|
|
311
|
+
<div style="display:contents">
|
|
295
312
|
{/* Toggle button with health indicator */}
|
|
296
313
|
<button
|
|
297
314
|
onclick={() => isOpen((v) => !v)}
|
|
@@ -312,9 +329,11 @@ export function DevPanel() {
|
|
|
312
329
|
W
|
|
313
330
|
</button>
|
|
314
331
|
|
|
315
|
-
{/* Panel -- conditionally rendered
|
|
332
|
+
{/* Panel -- conditionally rendered. _suppressDevtools: opening the
|
|
333
|
+
panel instantiates this whole subtree (dozens of bindings) — none
|
|
334
|
+
of them may register with devtools (see DevPanel docblock). */}
|
|
316
335
|
{() =>
|
|
317
|
-
isOpen() ? (
|
|
336
|
+
_suppressDevtools(() => isOpen() ? (
|
|
318
337
|
<div style={PANEL_STYLE}>
|
|
319
338
|
{/* Header */}
|
|
320
339
|
<div style="display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid #2a2a4a;background:#16163a;">
|
|
@@ -373,9 +392,10 @@ export function DevPanel() {
|
|
|
373
392
|
</button>
|
|
374
393
|
</div>
|
|
375
394
|
|
|
376
|
-
{/* Content
|
|
395
|
+
{/* Content. _suppressDevtools: tab switches and snapshot
|
|
396
|
+
updates rebuild this subtree — keep it out of devtools. */}
|
|
377
397
|
<div style="overflow-y:auto;flex:1;">
|
|
378
|
-
{() => {
|
|
398
|
+
{() => _suppressDevtools(() => {
|
|
379
399
|
const tab = activeTab();
|
|
380
400
|
if (tab === 'overview') return renderOverview();
|
|
381
401
|
if (tab === 'signals') return renderSignals();
|
|
@@ -383,12 +403,12 @@ export function DevPanel() {
|
|
|
383
403
|
if (tab === 'components') return renderComponents();
|
|
384
404
|
if (tab === 'errors') return renderErrors();
|
|
385
405
|
return renderOverview();
|
|
386
|
-
}}
|
|
406
|
+
})}
|
|
387
407
|
</div>
|
|
388
408
|
</div>
|
|
389
|
-
) : null
|
|
409
|
+
) : null)
|
|
390
410
|
}
|
|
391
|
-
|
|
411
|
+
</div>
|
|
392
412
|
);
|
|
393
413
|
}
|
|
394
414
|
|
package/src/index.js
CHANGED
|
@@ -19,6 +19,11 @@ let signalId = 0;
|
|
|
19
19
|
let effectId = 0;
|
|
20
20
|
let componentId = 0;
|
|
21
21
|
|
|
22
|
+
// what-core's installSignalReadGuardrail, captured when the core module is
|
|
23
|
+
// wired up in installDevTools(). Dev-only: warns when a signal is coerced
|
|
24
|
+
// to a string/number without being called (e.g. `Total: ${count}`).
|
|
25
|
+
let coreSignalReadGuardrail = null;
|
|
26
|
+
|
|
22
27
|
// Registries
|
|
23
28
|
const signals = new Map(); // id → { name, ref, createdAt, internal }
|
|
24
29
|
const effects = new Map(); // id → { name, createdAt, depSignalIds, runCount, lastRunAt }
|
|
@@ -84,6 +89,29 @@ function attributeToComponent() {
|
|
|
84
89
|
return null;
|
|
85
90
|
}
|
|
86
91
|
|
|
92
|
+
// --- Self-tracking suppression ---
|
|
93
|
+
// Devtools UI rendered INSIDE the inspected app (the DevPanel) must not
|
|
94
|
+
// register its own signals/effects: panel-internal registrations emit
|
|
95
|
+
// devtools events, the panel reacts by updating its snapshot signal, the
|
|
96
|
+
// resulting re-render creates more effects, which emit again — an unbounded
|
|
97
|
+
// feedback loop that wedges and crashes the page. Re-entrant (counter).
|
|
98
|
+
let suppressDepth = 0;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Run fn with devtools registration suppressed (exception-safe).
|
|
102
|
+
* Signals/effects/components created inside fn are invisible to devtools.
|
|
103
|
+
* Does NOT affect what-core's reactive dependency tracking.
|
|
104
|
+
* @internal Used by DevPanel; exported for devtools-adjacent UIs and tests.
|
|
105
|
+
*/
|
|
106
|
+
export function _suppressDevtools(fn) {
|
|
107
|
+
suppressDepth++;
|
|
108
|
+
try {
|
|
109
|
+
return fn();
|
|
110
|
+
} finally {
|
|
111
|
+
suppressDepth--;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
87
115
|
// Error log (capped at 100)
|
|
88
116
|
const errors = [];
|
|
89
117
|
const MAX_ERRORS = 100;
|
|
@@ -184,7 +212,7 @@ export function safeSerialize(value, depth = 0, seen) {
|
|
|
184
212
|
* Called from reactive.js __DEV__ hooks.
|
|
185
213
|
*/
|
|
186
214
|
export function registerSignal(sig, name) {
|
|
187
|
-
if (!installed) return;
|
|
215
|
+
if (!installed || suppressDepth > 0) return;
|
|
188
216
|
const id = ++signalId;
|
|
189
217
|
const entry = {
|
|
190
218
|
id,
|
|
@@ -200,6 +228,11 @@ export function registerSignal(sig, name) {
|
|
|
200
228
|
sig._devId = id;
|
|
201
229
|
// Reverse lookup for O(1) effect dep resolution
|
|
202
230
|
if (sig._subs) subsToSignalId.set(sig._subs, id);
|
|
231
|
+
// Dev guardrail: warn when this signal is string/number-coerced without
|
|
232
|
+
// being called (catches `Total: ${count}` — should be `${count()}`).
|
|
233
|
+
if (coreSignalReadGuardrail) {
|
|
234
|
+
try { coreSignalReadGuardrail(sig, entry.name); } catch {}
|
|
235
|
+
}
|
|
203
236
|
emit('signal:created', entry);
|
|
204
237
|
return id;
|
|
205
238
|
}
|
|
@@ -232,7 +265,7 @@ export function unregisterSignal(sig) {
|
|
|
232
265
|
* Register an effect with the devtools.
|
|
233
266
|
*/
|
|
234
267
|
export function registerEffect(e, name) {
|
|
235
|
-
if (!installed) return;
|
|
268
|
+
if (!installed || suppressDepth > 0) return;
|
|
236
269
|
const id = ++effectId;
|
|
237
270
|
const entry = {
|
|
238
271
|
id,
|
|
@@ -322,7 +355,7 @@ function captureError(err, context) {
|
|
|
322
355
|
* (see what_components / what_explain) so agents re-query before using IDs.
|
|
323
356
|
*/
|
|
324
357
|
export function registerComponent(name, element, parentDevId) {
|
|
325
|
-
if (!installed) return;
|
|
358
|
+
if (!installed || suppressDepth > 0) return;
|
|
326
359
|
const id = ++componentId;
|
|
327
360
|
const entry = {
|
|
328
361
|
id,
|
|
@@ -457,6 +490,11 @@ export function installDevTools(core) {
|
|
|
457
490
|
// Wire into what-core's reactive system
|
|
458
491
|
function installInto(mod) {
|
|
459
492
|
if (!mod) return;
|
|
493
|
+
// Capture the guardrail BEFORE hooks go live so the very first
|
|
494
|
+
// registrations (and the pre-install drain below) get wrapped too.
|
|
495
|
+
if (typeof mod.installSignalReadGuardrail === 'function') {
|
|
496
|
+
coreSignalReadGuardrail = mod.installSignalReadGuardrail;
|
|
497
|
+
}
|
|
460
498
|
if (mod.__setDevToolsHooks) mod.__setDevToolsHooks(hooks);
|
|
461
499
|
if (typeof window !== 'undefined') window.__WHAT_CORE__ = mod;
|
|
462
500
|
|