what-devtools 0.12.4 → 0.13.1

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 CHANGED
@@ -1,12 +1,18 @@
1
1
  {
2
2
  "name": "what-devtools",
3
- "version": "0.12.4",
3
+ "version": "0.13.1",
4
4
  "description": "Dev tools for What Framework — signal inspector, component tree, effect graph",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "exports": {
8
- ".": "./src/index.js",
9
- "./panel": "./src/DevPanel.jsx"
8
+ ".": {
9
+ "types": "./src/index.d.ts",
10
+ "default": "./src/index.js"
11
+ },
12
+ "./panel": {
13
+ "types": "./src/DevPanel.d.ts",
14
+ "default": "./src/DevPanel.jsx"
15
+ }
10
16
  },
11
17
  "files": [
12
18
  "src"
@@ -20,7 +26,7 @@
20
26
  "inspector"
21
27
  ],
22
28
  "peerDependencies": {
23
- "what-core": "^0.12.4"
29
+ "what-core": "^0.13.1"
24
30
  },
25
31
  "author": "ZVN DEV (https://zvndev.com)",
26
32
  "license": "MIT",
@@ -31,5 +37,6 @@
31
37
  "bugs": {
32
38
  "url": "https://github.com/CelsianJs/what-framework/issues"
33
39
  },
34
- "homepage": "https://whatfw.com"
40
+ "homepage": "https://whatfw.com",
41
+ "types": "./src/index.d.ts"
35
42
  }
@@ -0,0 +1,9 @@
1
+ // Types for `what-devtools/panel`.
2
+ import type { VNode } from 'what-core';
3
+
4
+ /**
5
+ * The in-page devtools panel. Mount it anywhere in a dev build; it reads from
6
+ * the same stores `installDevTools()` populates.
7
+ */
8
+ export function DevPanel(): VNode<any> | null;
9
+ export default DevPanel;
package/src/DevPanel.jsx CHANGED
@@ -19,7 +19,7 @@
19
19
  * Works WITHOUT MCP devtools connected.
20
20
  */
21
21
 
22
- import { signal, effect, onCleanup } from 'what-core';
22
+ import { signal, onCleanup } from 'what-core';
23
23
  import { subscribe, getSnapshot, getErrors, installDevTools, _suppressDevtools } from './index.js';
24
24
 
25
25
  export function DevPanel() {
@@ -111,11 +111,10 @@ function DevPanelBody() {
111
111
 
112
112
  // --- Tab: Overview ---
113
113
  const renderOverview = () => {
114
- const data = snapshot();
115
- const health = getHealth();
116
- const errs = recentErrors();
117
- const recentErrs = errs.slice(-3).reverse();
118
-
114
+ // Everything below reads snapshot()/getHealth()/recentErrors() inside a
115
+ // thunk so it stays reactive. Four eager copies used to be taken here and
116
+ // then shadowed; had anything rendered them directly it would have frozen
117
+ // at first paint.
119
118
  return (
120
119
  <div style="padding:12px;">
121
120
  {/* Health indicator */}
package/src/index.d.ts ADDED
@@ -0,0 +1,114 @@
1
+ // Hand-written to match src/index.js. `what-devtools` is imported
2
+ // programmatically (`installDevTools`, `getSnapshot`) and by the MCP bridge,
3
+ // so TypeScript consumers need these; the package shipped without any types
4
+ // until 0.13.1.
5
+
6
+ /** A tracked signal, as returned by {@link getSnapshot}. */
7
+ export interface DevToolsSignal {
8
+ id: number;
9
+ name: string;
10
+ /** Read with `peek()`, so taking a snapshot never creates a subscription. */
11
+ value: unknown;
12
+ componentId: number | null;
13
+ }
14
+
15
+ /** A tracked effect, as returned by {@link getSnapshot}. */
16
+ export interface DevToolsEffect {
17
+ id: number;
18
+ name: string;
19
+ depSignalIds: number[];
20
+ runCount: number;
21
+ /** `Date.now()` of the last run, or null if it has not run yet. */
22
+ lastRunAt: number | null;
23
+ componentId: number | null;
24
+ }
25
+
26
+ /** A mounted component, as returned by {@link getSnapshot}. */
27
+ export interface DevToolsComponent {
28
+ id: number;
29
+ name: string;
30
+ parentId: number | null;
31
+ }
32
+
33
+ /** A captured error. The buffer holds the most recent 100. */
34
+ export interface DevToolsError {
35
+ message: string;
36
+ stack: string | null;
37
+ /** Where it came from, e.g. 'effect' or 'unknown'. */
38
+ type: string;
39
+ effectId: number | null;
40
+ timestamp: number;
41
+ }
42
+
43
+ export interface DevToolsSnapshot {
44
+ signals: DevToolsSignal[];
45
+ effects: DevToolsEffect[];
46
+ components: DevToolsComponent[];
47
+ errors: DevToolsError[];
48
+ }
49
+
50
+ export type DevToolsEvent =
51
+ | 'signal:created'
52
+ | 'signal:updated'
53
+ | 'signal:disposed'
54
+ | 'effect:created'
55
+ | 'effect:run'
56
+ | 'effect:disposed'
57
+ | 'component:mounted'
58
+ | 'component:unmounted'
59
+ | 'error:captured';
60
+
61
+ /** Minimal shape of a what-core signal accessor, as devtools sees it. */
62
+ export interface TrackedSignal<T = unknown> {
63
+ (): T;
64
+ peek(): T;
65
+ }
66
+
67
+ /**
68
+ * Run `fn` with devtools registration suppressed, so framework-internal
69
+ * signals and effects created inside it are not reported to the panel.
70
+ */
71
+ export function _suppressDevtools<T>(fn: () => T): T;
72
+
73
+ /**
74
+ * Convert a value to something structured-cloneable and depth-limited, for
75
+ * sending over the MCP bridge.
76
+ */
77
+ export function safeSerialize(value: unknown, depth?: number, seen?: WeakSet<object>): unknown;
78
+
79
+ export function registerSignal(sig: TrackedSignal, name?: string): number;
80
+ export function notifySignalUpdate(sig: TrackedSignal): void;
81
+ export function unregisterSignal(sig: TrackedSignal): void;
82
+
83
+ export function registerEffect(e: object, name?: string): number;
84
+ export function unregisterEffect(e: object): void;
85
+
86
+ export function registerComponent(
87
+ name: string,
88
+ element: Node | null,
89
+ parentDevId?: number | null,
90
+ ): number;
91
+ export function unregisterComponent(id: number): void;
92
+
93
+ /** Subscribe to devtools events. Returns an unsubscribe function. */
94
+ export function subscribe(
95
+ fn: (event: DevToolsEvent, payload: unknown) => void,
96
+ ): () => void;
97
+
98
+ export function getSnapshot(opts?: { includeInternal?: boolean }): DevToolsSnapshot;
99
+
100
+ /** Captured errors, optionally only those after a `Date.now()` timestamp. */
101
+ export function getErrors(opts?: { since?: number }): DevToolsError[];
102
+
103
+ /**
104
+ * Install devtools. Call once at app startup. Wires into what-core's `__DEV__`
105
+ * hooks and exposes `window.__WHAT_DEVTOOLS__`. Idempotent.
106
+ *
107
+ * @param core Optional what-core module. Dynamically imported when omitted.
108
+ */
109
+ export function installDevTools(core?: object): void;
110
+
111
+ export const signals: Map<number, { name: string; ref: TrackedSignal; createdAt: number; internal?: boolean }>;
112
+ export const effects: Map<number, { name: string; createdAt: number; depSignalIds: number[]; runCount: number; lastRunAt: number | null }>;
113
+ export const components: Map<number, { name: string; element: Node | null; mountedAt: number; parentId: number | null }>;
114
+ export const errors: DevToolsError[];
package/src/index.js CHANGED
@@ -141,9 +141,11 @@ export function safeSerialize(value, depth = 0, seen) {
141
141
 
142
142
  // DOM nodes
143
143
  if (typeof Node !== 'undefined' && value instanceof Node) {
144
+ // id/className live on Element, not Node; a text or comment node has neither.
145
+ const el = /** @type {Element} */ (/** @type {unknown} */ (value));
144
146
  const tag = value.nodeName?.toLowerCase() || 'node';
145
- const id = value.id ? `#${value.id}` : '';
146
- const cls = value.className ? `.${String(value.className).split(' ')[0]}` : '';
147
+ const id = el.id ? `#${el.id}` : '';
148
+ const cls = el.className ? `.${String(el.className).split(' ')[0]}` : '';
147
149
  return `[DOM: <${tag}${id}${cls}>]`;
148
150
  }
149
151