what-devtools 0.5.3

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 ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "what-devtools",
3
+ "version": "0.5.3",
4
+ "description": "Dev tools for What Framework — signal inspector, component tree, effect graph",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./panel": "./src/DevPanel.jsx"
10
+ },
11
+ "files": [
12
+ "src"
13
+ ],
14
+ "keywords": [
15
+ "what",
16
+ "framework",
17
+ "devtools",
18
+ "signals",
19
+ "debug",
20
+ "inspector"
21
+ ],
22
+ "peerDependencies": {
23
+ "what-core": "^0.5.3"
24
+ },
25
+ "author": "",
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/zvndev/what-fw"
30
+ },
31
+ "bugs": {
32
+ "url": "https://github.com/zvndev/what-fw/issues"
33
+ },
34
+ "homepage": "https://whatframework.dev"
35
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * What Framework DevPanel
3
+ *
4
+ * A drop-in floating UI panel that shows live signal values,
5
+ * active effects, and mounted components during development.
6
+ *
7
+ * Usage:
8
+ * import { DevPanel } from 'what-devtools/panel';
9
+ * // Add to your app:
10
+ * <DevPanel />
11
+ *
12
+ * The panel is draggable and can be collapsed. It auto-updates
13
+ * when signals change.
14
+ */
15
+
16
+ import { signal, effect, onCleanup } from 'what-core';
17
+ import { subscribe, getSnapshot, installDevTools } from './index.js';
18
+
19
+ export function DevPanel() {
20
+ // Auto-install devtools if not already done
21
+ installDevTools();
22
+
23
+ const isOpen = signal(false);
24
+ const activeTab = signal('signals');
25
+ const snapshot = signal(getSnapshot());
26
+
27
+ // Subscribe to devtools events and refresh
28
+ const unsub = subscribe(() => {
29
+ snapshot(getSnapshot());
30
+ });
31
+
32
+ // Also poll every 500ms for signal value changes (cheap — just reads .peek())
33
+ const interval = setInterval(() => {
34
+ snapshot(getSnapshot());
35
+ }, 500);
36
+
37
+ onCleanup(() => {
38
+ unsub();
39
+ clearInterval(interval);
40
+ });
41
+
42
+ const panelStyle = () => isOpen()
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;';
45
+
46
+ const tabStyle = (tab) => () => {
47
+ const isActive = activeTab() === tab;
48
+ return `padding:6px 12px;border:none;background:${isActive ? '#2a2a4a' : 'transparent'};color:${isActive ? '#fff' : '#6a6a8a'};cursor:pointer;font-family:inherit;font-size:11px;font-weight:600;border-radius:4px;`;
49
+ };
50
+
51
+ const renderSignals = () => {
52
+ const data = snapshot();
53
+ if (!data.signals.length) {
54
+ return <div style="padding:12px;color:#4a4a6a;">No signals tracked</div>;
55
+ }
56
+ return (
57
+ <div style="padding:8px;">
58
+ {data.signals.map(s => (
59
+ <div key={s.id} style="display:flex;justify-content:space-between;align-items:center;padding:4px 8px;border-bottom:1px solid #2a2a4a;">
60
+ <span style="color:#818cf8;">{s.name}</span>
61
+ <span style="color:#a0a0c0;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">
62
+ {formatValue(s.value)}
63
+ </span>
64
+ </div>
65
+ ))}
66
+ </div>
67
+ );
68
+ };
69
+
70
+ const renderEffects = () => {
71
+ const data = snapshot();
72
+ if (!data.effects.length) {
73
+ return <div style="padding:12px;color:#4a4a6a;">No effects tracked</div>;
74
+ }
75
+ return (
76
+ <div style="padding:8px;">
77
+ {data.effects.map(e => (
78
+ <div key={e.id} style="padding:4px 8px;border-bottom:1px solid #2a2a4a;">
79
+ <span style="color:#fbbf24;">{e.name}</span>
80
+ </div>
81
+ ))}
82
+ </div>
83
+ );
84
+ };
85
+
86
+ const renderComponents = () => {
87
+ const data = snapshot();
88
+ if (!data.components.length) {
89
+ return <div style="padding:12px;color:#4a4a6a;">No components tracked</div>;
90
+ }
91
+ return (
92
+ <div style="padding:8px;">
93
+ {data.components.map(c => (
94
+ <div key={c.id} style="padding:4px 8px;border-bottom:1px solid #2a2a4a;">
95
+ <span style="color:#34d399;">&lt;{c.name} /&gt;</span>
96
+ </div>
97
+ ))}
98
+ </div>
99
+ );
100
+ };
101
+
102
+ return (
103
+ <>
104
+ {/* Toggle button */}
105
+ <button
106
+ onclick={() => isOpen(v => !v)}
107
+ style="position:fixed;bottom:12px;right:12px;z-index:99999;width:36px;height:36px;border-radius:8px;border:1px solid #2a2a4a;background:linear-gradient(135deg,#2563eb,#1d4ed8);color:#fff;font-weight:800;font-size:14px;cursor:pointer;font-family:ui-monospace,monospace;box-shadow:0 4px 12px rgba(37,99,235,0.3);"
108
+ title="What Framework DevTools"
109
+ >
110
+ W
111
+ </button>
112
+
113
+ {/* Panel */}
114
+ <div style={panelStyle}>
115
+ {/* Header */}
116
+ <div style="display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid #2a2a4a;background:#16163a;">
117
+ <div style="display:flex;align-items:center;gap:8px;">
118
+ <span style="font-weight:700;font-size:12px;color:#818cf8;">What DevTools</span>
119
+ </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
+
128
+ {/* Tabs */}
129
+ <div style="display:flex;gap:4px;padding:6px 8px;border-bottom:1px solid #2a2a4a;">
130
+ <button style={tabStyle('signals')} onclick={() => activeTab('signals')}>
131
+ Signals ({() => snapshot().signals.length})
132
+ </button>
133
+ <button style={tabStyle('effects')} onclick={() => activeTab('effects')}>
134
+ Effects ({() => snapshot().effects.length})
135
+ </button>
136
+ <button style={tabStyle('components')} onclick={() => activeTab('components')}>
137
+ Components ({() => snapshot().components.length})
138
+ </button>
139
+ </div>
140
+
141
+ {/* Content */}
142
+ <div style="overflow-y:auto;flex:1;">
143
+ {() => {
144
+ const tab = activeTab();
145
+ if (tab === 'signals') return renderSignals();
146
+ if (tab === 'effects') return renderEffects();
147
+ return renderComponents();
148
+ }}
149
+ </div>
150
+ </div>
151
+ </>
152
+ );
153
+ }
154
+
155
+ function formatValue(value) {
156
+ if (value === null) return 'null';
157
+ if (value === undefined) return 'undefined';
158
+ if (typeof value === 'string') return `"${value.length > 30 ? value.slice(0, 30) + '...' : value}"`;
159
+ if (typeof value === 'object') {
160
+ try {
161
+ const str = JSON.stringify(value);
162
+ return str.length > 40 ? str.slice(0, 40) + '...' : str;
163
+ } catch {
164
+ return '[Object]';
165
+ }
166
+ }
167
+ return String(value);
168
+ }
169
+
170
+ export default DevPanel;
package/src/index.js ADDED
@@ -0,0 +1,204 @@
1
+ /**
2
+ * What Framework DevTools
3
+ *
4
+ * Runtime instrumentation for debugging signals, effects, and components.
5
+ * In dev mode, exposes a `window.__WHAT_DEVTOOLS__` global for inspection.
6
+ *
7
+ * Usage:
8
+ * import { installDevTools } from 'what-devtools';
9
+ * installDevTools(); // Call once at app entry
10
+ *
11
+ * Then inspect in console:
12
+ * __WHAT_DEVTOOLS__.signals // Map of all live signals
13
+ * __WHAT_DEVTOOLS__.components // Map of mounted components
14
+ * __WHAT_DEVTOOLS__.effects // Map of active effects
15
+ */
16
+
17
+ let installed = false;
18
+ let signalId = 0;
19
+ let effectId = 0;
20
+ let componentId = 0;
21
+
22
+ // Registries
23
+ const signals = new Map(); // id → { name, value, subs, createdAt }
24
+ const effects = new Map(); // id → { name, deps, createdAt }
25
+ const components = new Map(); // id → { name, mountedAt, signals: [], effects: [] }
26
+
27
+ // Event listeners for the DevPanel
28
+ const listeners = new Set();
29
+
30
+ function emit(event, data) {
31
+ for (const fn of listeners) {
32
+ try { fn(event, data); } catch {}
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Register a signal with the devtools.
38
+ * Called from reactive.js __DEV__ hooks.
39
+ */
40
+ export function registerSignal(sig, name) {
41
+ if (!installed) return;
42
+ const id = ++signalId;
43
+ const entry = {
44
+ id,
45
+ name: name || `signal_${id}`,
46
+ ref: sig,
47
+ createdAt: Date.now(),
48
+ };
49
+ signals.set(id, entry);
50
+ sig._devId = id;
51
+ emit('signal:created', entry);
52
+ return id;
53
+ }
54
+
55
+ /**
56
+ * Notify devtools that a signal value changed.
57
+ */
58
+ export function notifySignalUpdate(sig) {
59
+ if (!installed) return;
60
+ const id = sig._devId;
61
+ if (id == null) return;
62
+ const entry = signals.get(id);
63
+ if (entry) {
64
+ emit('signal:updated', { id, name: entry.name, value: sig.peek() });
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Unregister a signal (when disposed via createRoot cleanup).
70
+ */
71
+ export function unregisterSignal(sig) {
72
+ if (!installed) return;
73
+ const id = sig._devId;
74
+ if (id == null) return;
75
+ signals.delete(id);
76
+ emit('signal:disposed', { id });
77
+ }
78
+
79
+ /**
80
+ * Register an effect with the devtools.
81
+ */
82
+ export function registerEffect(e, name) {
83
+ if (!installed) return;
84
+ const id = ++effectId;
85
+ const entry = {
86
+ id,
87
+ name: name || e.fn?.name || `effect_${id}`,
88
+ createdAt: Date.now(),
89
+ };
90
+ effects.set(id, entry);
91
+ e._devId = id;
92
+ emit('effect:created', entry);
93
+ return id;
94
+ }
95
+
96
+ /**
97
+ * Unregister an effect.
98
+ */
99
+ export function unregisterEffect(e) {
100
+ if (!installed) return;
101
+ const id = e._devId;
102
+ if (id == null) return;
103
+ effects.delete(id);
104
+ emit('effect:disposed', { id });
105
+ }
106
+
107
+ /**
108
+ * Register a component mount.
109
+ */
110
+ export function registerComponent(name, element) {
111
+ if (!installed) return;
112
+ const id = ++componentId;
113
+ const entry = {
114
+ id,
115
+ name: name || 'Anonymous',
116
+ element,
117
+ mountedAt: Date.now(),
118
+ };
119
+ components.set(id, entry);
120
+ emit('component:mounted', entry);
121
+ return id;
122
+ }
123
+
124
+ /**
125
+ * Unregister a component (unmount).
126
+ */
127
+ export function unregisterComponent(id) {
128
+ if (!installed) return;
129
+ components.delete(id);
130
+ emit('component:unmounted', { id });
131
+ }
132
+
133
+ /**
134
+ * Subscribe to devtools events.
135
+ * Returns an unsubscribe function.
136
+ */
137
+ export function subscribe(fn) {
138
+ listeners.add(fn);
139
+ return () => listeners.delete(fn);
140
+ }
141
+
142
+ /**
143
+ * Get a snapshot of all tracked state.
144
+ */
145
+ export function getSnapshot() {
146
+ const signalList = [];
147
+ for (const [id, entry] of signals) {
148
+ signalList.push({
149
+ id,
150
+ name: entry.name,
151
+ value: entry.ref.peek(),
152
+ });
153
+ }
154
+
155
+ const effectList = [];
156
+ for (const [id, entry] of effects) {
157
+ effectList.push({ id, name: entry.name });
158
+ }
159
+
160
+ const componentList = [];
161
+ for (const [id, entry] of components) {
162
+ componentList.push({ id, name: entry.name });
163
+ }
164
+
165
+ return { signals: signalList, effects: effectList, components: componentList };
166
+ }
167
+
168
+ /**
169
+ * Install devtools. Call once at app startup.
170
+ * Wires into what-core's __DEV__ hooks and exposes `window.__WHAT_DEVTOOLS__`.
171
+ */
172
+ export function installDevTools() {
173
+ if (installed) return;
174
+ installed = true;
175
+
176
+ // Wire into what-core's reactive system
177
+ try {
178
+ import('what-core').then(core => {
179
+ if (core.__setDevToolsHooks) {
180
+ core.__setDevToolsHooks({
181
+ onSignalCreate: (sig) => registerSignal(sig),
182
+ onSignalUpdate: (sig) => notifySignalUpdate(sig),
183
+ onEffectCreate: (e) => registerEffect(e),
184
+ onEffectDispose: (e) => unregisterEffect(e),
185
+ });
186
+ }
187
+ }).catch(() => {
188
+ // what-core not available — devtools still work via manual registration
189
+ });
190
+ } catch {}
191
+
192
+ if (typeof window !== 'undefined') {
193
+ window.__WHAT_DEVTOOLS__ = {
194
+ get signals() { return getSnapshot().signals; },
195
+ get effects() { return getSnapshot().effects; },
196
+ get components() { return getSnapshot().components; },
197
+ getSnapshot,
198
+ subscribe,
199
+ _registries: { signals, effects, components },
200
+ };
201
+ }
202
+ }
203
+
204
+ export { signals, effects, components };