@pulse-js/tools 0.1.4 → 0.1.6
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 +10 -7
- package/dist/index.cjs +496 -202
- package/dist/index.d.cts +21 -8
- package/dist/index.d.ts +21 -8
- package/dist/index.js +496 -202
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -27,32 +27,47 @@ module.exports = __toCommonJS(index_exports);
|
|
|
27
27
|
// src/pulse-inspector.ts
|
|
28
28
|
var import_core = require("@pulse-js/core");
|
|
29
29
|
var COLORS = {
|
|
30
|
-
bg: "rgba(13, 13, 18, 0.
|
|
30
|
+
bg: "rgba(13, 13, 18, 0.96)",
|
|
31
31
|
border: "rgba(255, 255, 255, 0.1)",
|
|
32
32
|
accent: "linear-gradient(135deg, #00d2ff 0%, #3a7bd5 100%)",
|
|
33
|
+
accentColor: "#00d2ff",
|
|
33
34
|
error: "#ff4b2b",
|
|
34
35
|
success: "#00f260",
|
|
35
36
|
pending: "#fdbb2d",
|
|
36
37
|
text: "#ffffff",
|
|
37
38
|
secondaryText: "#a0a0a0",
|
|
38
|
-
cardBg: "rgba(255, 255, 255, 0.05)"
|
|
39
|
+
cardBg: "rgba(255, 255, 255, 0.05)",
|
|
40
|
+
cardHover: "rgba(255, 255, 255, 0.08)",
|
|
41
|
+
inputBg: "rgba(0,0,0,0.3)"
|
|
39
42
|
};
|
|
40
|
-
var
|
|
43
|
+
var ICONS = {
|
|
44
|
+
tree: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12h-4l-3 8-4-16-3 8H2"/></svg>`,
|
|
45
|
+
list: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>`,
|
|
46
|
+
chevronRight: `<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>`,
|
|
47
|
+
chevronDown: `<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg>`,
|
|
48
|
+
edit: `<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>`
|
|
49
|
+
};
|
|
50
|
+
var STORAGE_KEY = "pulse-devtools-state";
|
|
41
51
|
var PulseInspector = class extends HTMLElement {
|
|
42
52
|
shadow;
|
|
43
53
|
units = [];
|
|
44
|
-
|
|
45
|
-
|
|
54
|
+
// State
|
|
55
|
+
state = {
|
|
56
|
+
pos: { x: window.innerWidth - 360, y: 20 },
|
|
57
|
+
isOpen: true,
|
|
58
|
+
activeTab: "inspector",
|
|
59
|
+
expandedNodes: []
|
|
60
|
+
};
|
|
46
61
|
isDragging = false;
|
|
47
62
|
offset = { x: 0, y: 0 };
|
|
48
|
-
|
|
49
|
-
|
|
63
|
+
editingUnit = null;
|
|
64
|
+
editValue = "";
|
|
50
65
|
unsubscribeRegistry;
|
|
51
66
|
unitSubscriptions = /* @__PURE__ */ new Map();
|
|
52
67
|
constructor() {
|
|
53
68
|
super();
|
|
54
69
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
55
|
-
this.
|
|
70
|
+
this.loadState();
|
|
56
71
|
}
|
|
57
72
|
connectedCallback() {
|
|
58
73
|
this.render();
|
|
@@ -62,22 +77,27 @@ var PulseInspector = class extends HTMLElement {
|
|
|
62
77
|
this.refreshUnits();
|
|
63
78
|
});
|
|
64
79
|
window.addEventListener("keydown", this.handleKeyDown);
|
|
80
|
+
window.addEventListener("resize", this.handleResize);
|
|
65
81
|
}
|
|
66
82
|
disconnectedCallback() {
|
|
67
83
|
if (this.unsubscribeRegistry) this.unsubscribeRegistry();
|
|
68
84
|
this.unitSubscriptions.forEach((unsub) => unsub());
|
|
69
85
|
window.removeEventListener("keydown", this.handleKeyDown);
|
|
86
|
+
window.removeEventListener("resize", this.handleResize);
|
|
70
87
|
}
|
|
71
|
-
|
|
88
|
+
loadState() {
|
|
72
89
|
try {
|
|
73
90
|
const saved = localStorage.getItem(STORAGE_KEY);
|
|
74
|
-
if (saved)
|
|
91
|
+
if (saved) {
|
|
92
|
+
const parsed = JSON.parse(saved);
|
|
93
|
+
this.state = { ...this.state, ...parsed };
|
|
94
|
+
}
|
|
75
95
|
} catch (e) {
|
|
76
96
|
}
|
|
77
97
|
}
|
|
78
|
-
|
|
98
|
+
saveState() {
|
|
79
99
|
try {
|
|
80
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(this.
|
|
100
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(this.state));
|
|
81
101
|
} catch (e) {
|
|
82
102
|
}
|
|
83
103
|
}
|
|
@@ -97,273 +117,547 @@ var PulseInspector = class extends HTMLElement {
|
|
|
97
117
|
});
|
|
98
118
|
}
|
|
99
119
|
setupListeners() {
|
|
100
|
-
this.shadow.addEventListener("mousedown",
|
|
120
|
+
this.shadow.addEventListener("mousedown", this.handleMouseDown);
|
|
121
|
+
this.shadow.addEventListener("click", this.handleClick);
|
|
122
|
+
this.shadow.addEventListener("submit", this.handleEditSubmit);
|
|
123
|
+
this.shadow.addEventListener("keydown", this.handleEditKeydown);
|
|
101
124
|
}
|
|
102
125
|
handleKeyDown = (e) => {
|
|
103
|
-
if (e.ctrlKey && e.key.toLowerCase() === "
|
|
126
|
+
if (e.ctrlKey && e.key.toLowerCase() === "m") {
|
|
104
127
|
e.preventDefault();
|
|
105
128
|
this.toggle();
|
|
106
129
|
}
|
|
107
130
|
};
|
|
131
|
+
handleResize = () => {
|
|
132
|
+
const maxW = window.innerWidth - (this.state.isOpen ? 400 : 120);
|
|
133
|
+
const maxH = window.innerHeight - (this.state.isOpen ? 500 : 45);
|
|
134
|
+
this.state.pos.x = Math.max(0, Math.min(maxW, this.state.pos.x));
|
|
135
|
+
this.state.pos.y = Math.max(0, Math.min(maxH, this.state.pos.y));
|
|
136
|
+
this.render();
|
|
137
|
+
};
|
|
108
138
|
toggle() {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
this.position.x = Math.max(0, Math.min(window.innerWidth - 350, this.position.x));
|
|
113
|
-
this.position.y = Math.max(0, Math.min(window.innerHeight - 450, this.position.y));
|
|
114
|
-
} else {
|
|
115
|
-
this.position.x = Math.max(0, Math.min(window.innerWidth - 120, this.position.x));
|
|
116
|
-
this.position.y = Math.max(0, Math.min(window.innerHeight - 45, this.position.y));
|
|
117
|
-
}
|
|
118
|
-
this.savePosition();
|
|
119
|
-
this.render();
|
|
120
|
-
}
|
|
139
|
+
this.state.isOpen = !this.state.isOpen;
|
|
140
|
+
this.saveState();
|
|
141
|
+
this.render();
|
|
121
142
|
}
|
|
122
|
-
|
|
123
|
-
|
|
143
|
+
// --- DRAG LOGIC ---
|
|
144
|
+
totalDragDistance = 0;
|
|
145
|
+
handleMouseDown = (e) => {
|
|
146
|
+
const me = e;
|
|
147
|
+
const target = me.target;
|
|
124
148
|
const isHeader = target.closest(".header") || target.classList.contains("toggle-btn");
|
|
125
|
-
|
|
149
|
+
const isClose = target.closest("#close");
|
|
150
|
+
if (!isHeader || isClose) return;
|
|
126
151
|
this.isDragging = true;
|
|
127
|
-
this.
|
|
128
|
-
this.lastMousePos = { x: e.clientX, y: e.clientY };
|
|
152
|
+
this.totalDragDistance = 0;
|
|
129
153
|
this.offset = {
|
|
130
|
-
x:
|
|
131
|
-
y:
|
|
154
|
+
x: me.clientX - this.state.pos.x,
|
|
155
|
+
y: me.clientY - this.state.pos.y
|
|
132
156
|
};
|
|
133
|
-
const
|
|
157
|
+
const move = (ev) => {
|
|
134
158
|
if (!this.isDragging) return;
|
|
135
|
-
|
|
136
|
-
this.
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
this.
|
|
140
|
-
x: Math.max(0, Math.min(window.innerWidth -
|
|
141
|
-
y: Math.max(0, Math.min(window.innerHeight -
|
|
159
|
+
ev.preventDefault();
|
|
160
|
+
this.totalDragDistance += Math.abs(ev.movementX) + Math.abs(ev.movementY);
|
|
161
|
+
const width = this.state.isOpen ? 400 : 140;
|
|
162
|
+
const height = this.state.isOpen ? 600 : 48;
|
|
163
|
+
this.state.pos = {
|
|
164
|
+
x: Math.max(0, Math.min(window.innerWidth - width, ev.clientX - this.offset.x)),
|
|
165
|
+
y: Math.max(0, Math.min(window.innerHeight - height, ev.clientY - this.offset.y))
|
|
142
166
|
};
|
|
143
167
|
const container = this.shadow.querySelector(".container");
|
|
144
168
|
if (container) {
|
|
145
|
-
container.style.left = `${this.
|
|
146
|
-
container.style.top = `${this.
|
|
169
|
+
container.style.left = `${this.state.pos.x}px`;
|
|
170
|
+
container.style.top = `${this.state.pos.y}px`;
|
|
147
171
|
}
|
|
148
172
|
};
|
|
149
|
-
const
|
|
173
|
+
const up = () => {
|
|
150
174
|
this.isDragging = false;
|
|
151
|
-
this.
|
|
152
|
-
document.removeEventListener("mousemove",
|
|
153
|
-
document.removeEventListener("mouseup",
|
|
175
|
+
this.saveState();
|
|
176
|
+
document.removeEventListener("mousemove", move);
|
|
177
|
+
document.removeEventListener("mouseup", up);
|
|
154
178
|
};
|
|
155
|
-
document.addEventListener("mousemove",
|
|
156
|
-
document.addEventListener("mouseup",
|
|
157
|
-
}
|
|
179
|
+
document.addEventListener("mousemove", move);
|
|
180
|
+
document.addEventListener("mouseup", up);
|
|
181
|
+
};
|
|
182
|
+
// --- INTERACTION LOGIC ---
|
|
183
|
+
handleClick = (e) => {
|
|
184
|
+
if (this.totalDragDistance > 5) {
|
|
185
|
+
this.totalDragDistance = 0;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const target = e.target;
|
|
189
|
+
if (target.id === "toggle" || target.closest("#close")) {
|
|
190
|
+
this.toggle();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const tabEl = target.closest(".tab-btn");
|
|
194
|
+
if (tabEl) {
|
|
195
|
+
const tab = tabEl.getAttribute("data-tab");
|
|
196
|
+
if (tab) {
|
|
197
|
+
this.state.activeTab = tab;
|
|
198
|
+
this.saveState();
|
|
199
|
+
this.render();
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const valueEl = target.closest(".editable-value");
|
|
204
|
+
if (valueEl) {
|
|
205
|
+
const name = valueEl.getAttribute("data-name");
|
|
206
|
+
const unit = this.units.find((u) => u._name === name);
|
|
207
|
+
const hasName = unit._name && unit._name !== "unnamed";
|
|
208
|
+
if (unit && !("state" in unit) && hasName) {
|
|
209
|
+
this.editingUnit = unit;
|
|
210
|
+
this.editValue = JSON.stringify(unit());
|
|
211
|
+
this.render();
|
|
212
|
+
requestAnimationFrame(() => {
|
|
213
|
+
const input = this.shadow.querySelector(".edit-input");
|
|
214
|
+
if (input) {
|
|
215
|
+
input.focus();
|
|
216
|
+
input.select();
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (this.editingUnit && !target.closest(".edit-form")) {
|
|
223
|
+
this.editingUnit = null;
|
|
224
|
+
this.render();
|
|
225
|
+
}
|
|
226
|
+
const treeNode = target.closest(".tree-toggle");
|
|
227
|
+
if (treeNode) {
|
|
228
|
+
e.stopPropagation();
|
|
229
|
+
const id = treeNode.getAttribute("data-id");
|
|
230
|
+
if (id) {
|
|
231
|
+
if (this.state.expandedNodes.includes(id)) {
|
|
232
|
+
this.state.expandedNodes = this.state.expandedNodes.filter((n) => n !== id);
|
|
233
|
+
} else {
|
|
234
|
+
this.state.expandedNodes.push(id);
|
|
235
|
+
}
|
|
236
|
+
this.saveState();
|
|
237
|
+
this.render();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
handleEditSubmit = (e) => {
|
|
242
|
+
e.preventDefault();
|
|
243
|
+
if (!this.editingUnit) return;
|
|
244
|
+
try {
|
|
245
|
+
const form = e.target;
|
|
246
|
+
const input = form.querySelector("input");
|
|
247
|
+
let val = input.value;
|
|
248
|
+
try {
|
|
249
|
+
if (val === "true") val = true;
|
|
250
|
+
else if (val === "false") val = false;
|
|
251
|
+
else if (val === "null") val = null;
|
|
252
|
+
else if (val === "undefined") val = void 0;
|
|
253
|
+
else if (!isNaN(Number(val)) && val.trim() !== "") val = Number(val);
|
|
254
|
+
else if (val.startsWith("{") || val.startsWith("[")) val = JSON.parse(val);
|
|
255
|
+
} catch (err) {
|
|
256
|
+
}
|
|
257
|
+
this.editingUnit.set(val);
|
|
258
|
+
this.editingUnit = null;
|
|
259
|
+
this.render();
|
|
260
|
+
} catch (err) {
|
|
261
|
+
console.error("Pulse DevTools: Failed to update value", err);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
handleEditKeydown = (e) => {
|
|
265
|
+
if (this.editingUnit && e.key === "Escape") {
|
|
266
|
+
this.editingUnit = null;
|
|
267
|
+
this.render();
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
// --- RENDERERS ---
|
|
158
271
|
render() {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
272
|
+
if (!this.state.isOpen) {
|
|
273
|
+
this.shadow.innerHTML = this.getStyles() + `
|
|
274
|
+
<div class="container" style="left: ${this.state.pos.x}px; top: ${this.state.pos.y}px">
|
|
275
|
+
<div class="glass" style="border-radius:30px; width:auto;">
|
|
276
|
+
<div class="toggle-btn" id="toggle">
|
|
277
|
+
<div class="dot"></div>
|
|
278
|
+
Pulse (${this.units.length})
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
`;
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
this.shadow.innerHTML = this.getStyles() + `
|
|
286
|
+
<div class="container" style="left: ${this.state.pos.x}px; top: ${this.state.pos.y}px">
|
|
287
|
+
<div class="glass">
|
|
288
|
+
|
|
289
|
+
<div class="header">
|
|
290
|
+
<div style="display:flex;align-items:center;gap:10px">
|
|
291
|
+
<div class="dot" style="width:10px;height:10px"></div>
|
|
292
|
+
<strong style="font-size:14px;letter-spacing:0.5px">PULSE</strong>
|
|
293
|
+
<span style="font-size:10px;opacity:0.5;margin-top:2px">v0.2.0</span>
|
|
294
|
+
</div>
|
|
295
|
+
<div id="close" class="icon-btn">\xD7</div>
|
|
296
|
+
</div>
|
|
297
|
+
|
|
298
|
+
<div class="tabs">
|
|
299
|
+
<div class="tab-btn ${this.state.activeTab === "inspector" ? "active" : ""}" data-tab="inspector">
|
|
300
|
+
${ICONS.list} Inspector
|
|
301
|
+
</div>
|
|
302
|
+
<div class="tab-btn ${this.state.activeTab === "tree" ? "active" : ""}" data-tab="tree">
|
|
303
|
+
${ICONS.tree} Pulse Tree
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
|
|
307
|
+
<div class="content">
|
|
308
|
+
${this.state.activeTab === "inspector" ? this.renderInspector() : this.renderTree()}
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
`;
|
|
314
|
+
}
|
|
315
|
+
renderInspector() {
|
|
316
|
+
if (this.units.length === 0) {
|
|
317
|
+
return `<div class="empty-state">No Pulse units detected.</div>`;
|
|
318
|
+
}
|
|
319
|
+
return `
|
|
320
|
+
<div class="list">
|
|
321
|
+
${this.units.map((u) => this.renderUnitCard(u)).join("")}
|
|
322
|
+
</div>
|
|
323
|
+
`;
|
|
324
|
+
}
|
|
325
|
+
renderUnitCard(unit) {
|
|
326
|
+
const isGuard = "state" in unit;
|
|
327
|
+
const name = unit._name || "unnamed";
|
|
328
|
+
const explanation = isGuard ? unit.explain() : null;
|
|
329
|
+
const value = isGuard ? explanation.value : unit();
|
|
330
|
+
let status = "ok";
|
|
331
|
+
if (isGuard) status = explanation.status;
|
|
332
|
+
const isEditing = this.editingUnit === unit;
|
|
333
|
+
return `
|
|
334
|
+
<div class="unit-card ${isGuard ? "" : "source-card"}" style="border-left-color: ${this.getStatusColor(status)}">
|
|
335
|
+
<div class="unit-header">
|
|
336
|
+
<div style="display:flex;align-items:center;gap:6px">
|
|
337
|
+
<div class="status-dot status-${status}"></div>
|
|
338
|
+
<strong title="${name}">${name}</strong>
|
|
339
|
+
</div>
|
|
340
|
+
<span class="badg type-${isGuard ? "guard" : "source"}">${isGuard ? "GUARD" : "SOURCE"}</span>
|
|
341
|
+
</div>
|
|
342
|
+
|
|
343
|
+
<div class="unit-body">
|
|
344
|
+
${isEditing ? `
|
|
345
|
+
<form class="edit-form">
|
|
346
|
+
<input class="edit-input" value='${this.safeStringify(value)}' />
|
|
347
|
+
</form>
|
|
348
|
+
` : `
|
|
349
|
+
<div class="value-row ${!isGuard && name !== "unnamed" ? "editable-value" : ""}"
|
|
350
|
+
data-name="${name}"
|
|
351
|
+
title="${!isGuard ? name === "unnamed" ? "Unnamed sources cannot be edited (not trackable)" : "Click to edit" : ""}">
|
|
352
|
+
<span style="opacity:0.5;margin-right:6px">Value:</span>
|
|
353
|
+
<span class="value-text">${this.formatValue(value)}</span>
|
|
354
|
+
${!isGuard && name !== "unnamed" ? `<span class="edit-icon">${ICONS.edit}</span>` : ""}
|
|
355
|
+
${!isGuard && name === "unnamed" ? `<span style="opacity:0.3;font-size:10px;margin-left:auto">\u{1F512} Locked</span>` : ""}
|
|
356
|
+
</div>
|
|
357
|
+
`}
|
|
358
|
+
|
|
359
|
+
${isGuard && explanation.status === "fail" ? this.renderReason(explanation.reason) : ""}
|
|
360
|
+
${isGuard && explanation.status === "pending" ? `
|
|
361
|
+
<div class="reason pending-reason">
|
|
362
|
+
\u23F3 Evaluating... ${explanation.lastReason ? `<br/><span style="opacity:0.7;font-size:9px">Last error: ${this.formatReasonText(explanation.lastReason)}</span>` : ""}
|
|
363
|
+
</div>
|
|
364
|
+
` : ""}
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
`;
|
|
368
|
+
}
|
|
369
|
+
renderTree() {
|
|
370
|
+
const guards = this.units.filter((u) => "state" in u);
|
|
371
|
+
if (guards.length === 0) return `<div class="empty-state">No Guards to visualize.</div>`;
|
|
372
|
+
return `
|
|
373
|
+
<div class="tree-view">
|
|
374
|
+
${guards.map((g) => this.renderTreeNode(g, 0, g._name)).join("")}
|
|
375
|
+
</div>
|
|
376
|
+
`;
|
|
377
|
+
}
|
|
378
|
+
renderTreeNode(guard, depth, key) {
|
|
379
|
+
const isExpanded = this.state.expandedNodes.includes(key);
|
|
380
|
+
const explanation = guard.explain();
|
|
381
|
+
const deps = explanation.dependencies || [];
|
|
382
|
+
const hasDeps = deps.length > 0;
|
|
383
|
+
const statusColor = this.getStatusColor(explanation.status);
|
|
384
|
+
return `
|
|
385
|
+
<div class="tree-node" style="padding-left:${depth * 12}px">
|
|
386
|
+
<div class="tree-row tree-toggle" data-id="${key}">
|
|
387
|
+
<span class="tree-icon" style="visibility:${hasDeps ? "visible" : "hidden"}">
|
|
388
|
+
${isExpanded ? ICONS.chevronDown : ICONS.chevronRight}
|
|
389
|
+
</span>
|
|
390
|
+
<span class="status-dot status-${explanation.status}" style="width:6px;height:6px;margin:0 6px"></span>
|
|
391
|
+
<span class="tree-name" style="color:${explanation.status === "fail" ? COLORS.error : "inherit"}">
|
|
392
|
+
${explanation.name}
|
|
393
|
+
</span>
|
|
394
|
+
${explanation.status === "fail" ? `
|
|
395
|
+
<span class="mini-badge fail">!</span>
|
|
396
|
+
` : ""}
|
|
397
|
+
</div>
|
|
398
|
+
</div>
|
|
399
|
+
${isExpanded && hasDeps ? `
|
|
400
|
+
<div class="tree-children">
|
|
401
|
+
${deps.map((dep) => {
|
|
402
|
+
const unit = this.units.find((u) => u._name === dep.name);
|
|
403
|
+
const childKey = key + "-" + dep.name;
|
|
404
|
+
if (unit && "state" in unit) {
|
|
405
|
+
return this.renderTreeNode(unit, depth + 1, childKey);
|
|
406
|
+
} else {
|
|
407
|
+
return `
|
|
408
|
+
<div class="tree-node" style="padding-left:${(depth + 1) * 12}px">
|
|
409
|
+
<div class="tree-row">
|
|
410
|
+
<span class="tree-icon" style="visibility:hidden"></span>
|
|
411
|
+
<span class="status-dot status-ok" style="background:${COLORS.secondaryText}"></span>
|
|
412
|
+
<span class="tree-name" style="opacity:0.7">${dep.name}</span>
|
|
413
|
+
<span class="mini-badge source">S</span>
|
|
414
|
+
</div>
|
|
415
|
+
</div>
|
|
416
|
+
`;
|
|
417
|
+
}
|
|
418
|
+
}).join("")}
|
|
419
|
+
</div>
|
|
420
|
+
` : ""}
|
|
421
|
+
`;
|
|
422
|
+
}
|
|
423
|
+
// --- HELPERS ---
|
|
424
|
+
renderReason(reason) {
|
|
425
|
+
if (!reason) return "";
|
|
426
|
+
const text = this.formatReasonText(reason);
|
|
427
|
+
const meta = typeof reason === "object" && reason.meta ? reason.meta : null;
|
|
428
|
+
return `
|
|
429
|
+
<div class="reason">
|
|
430
|
+
<strong>${typeof reason === "object" ? reason.code || "ERROR" : "FAIL"}</strong>
|
|
431
|
+
<div>${text}</div>
|
|
432
|
+
${meta ? `<pre class="meta-block">${JSON.stringify(meta, null, 2)}</pre>` : ""}
|
|
433
|
+
</div>
|
|
434
|
+
`;
|
|
435
|
+
}
|
|
436
|
+
formatReasonText(reason) {
|
|
437
|
+
if (typeof reason === "string") return reason;
|
|
438
|
+
return reason.message || JSON.stringify(reason);
|
|
439
|
+
}
|
|
440
|
+
formatValue(val) {
|
|
441
|
+
if (val === void 0) return "undefined";
|
|
442
|
+
if (val === null) return "null";
|
|
443
|
+
if (typeof val === "function") return "\u0192()";
|
|
444
|
+
if (typeof val === "object") return "{...}";
|
|
445
|
+
return String(val);
|
|
446
|
+
}
|
|
447
|
+
safeStringify(val) {
|
|
448
|
+
try {
|
|
449
|
+
if (val === void 0) return "undefined";
|
|
450
|
+
return JSON.stringify(val);
|
|
451
|
+
} catch {
|
|
452
|
+
return String(val);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
getStatusColor(status) {
|
|
456
|
+
switch (status) {
|
|
457
|
+
case "ok":
|
|
458
|
+
return COLORS.success;
|
|
459
|
+
case "fail":
|
|
460
|
+
return COLORS.error;
|
|
461
|
+
case "pending":
|
|
462
|
+
return COLORS.pending;
|
|
463
|
+
default:
|
|
464
|
+
return COLORS.secondaryText;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
getStyles() {
|
|
468
|
+
return `
|
|
162
469
|
<style>
|
|
163
|
-
:host {
|
|
164
|
-
|
|
165
|
-
|
|
470
|
+
:host { all: initial; font-family: 'Inter', -apple-system, sans-serif; }
|
|
471
|
+
* { box-sizing: border-box; }
|
|
472
|
+
|
|
166
473
|
.container {
|
|
167
474
|
position: fixed;
|
|
168
|
-
|
|
169
|
-
top: ${this.position.y}px;
|
|
170
|
-
width: ${w}px;
|
|
475
|
+
height: auto;
|
|
171
476
|
z-index: 999999;
|
|
172
|
-
|
|
173
|
-
color:
|
|
174
|
-
user-select: none;
|
|
477
|
+
filter: drop-shadow(0 8px 32px rgba(0,0,0,0.5));
|
|
478
|
+
color: ${COLORS.text};
|
|
175
479
|
}
|
|
480
|
+
|
|
176
481
|
.glass {
|
|
177
482
|
background: ${COLORS.bg};
|
|
178
|
-
backdrop-filter: blur(
|
|
179
|
-
-webkit-backdrop-filter: blur(20px);
|
|
483
|
+
backdrop-filter: blur(12px);
|
|
180
484
|
border: 1px solid ${COLORS.border};
|
|
181
|
-
border-radius:
|
|
182
|
-
box-shadow: 0 10px 40px rgba(0,0,0,0.5);
|
|
485
|
+
border-radius: 12px;
|
|
183
486
|
overflow: hidden;
|
|
184
|
-
transition: border-radius 0.2s ease;
|
|
185
487
|
display: flex;
|
|
186
488
|
flex-direction: column;
|
|
489
|
+
width: 400px;
|
|
490
|
+
max-height: 80vh;
|
|
491
|
+
transition: width 0.2s, height 0.2s;
|
|
187
492
|
}
|
|
493
|
+
|
|
494
|
+
/* TOGGLE BUTTON MODE */
|
|
188
495
|
.toggle-btn {
|
|
189
|
-
width:
|
|
190
|
-
height:
|
|
496
|
+
width: 140px;
|
|
497
|
+
height: 48px;
|
|
191
498
|
display: flex;
|
|
192
499
|
align-items: center;
|
|
193
500
|
justify-content: center;
|
|
194
|
-
|
|
501
|
+
gap: 10px;
|
|
502
|
+
cursor: pointer;
|
|
195
503
|
font-weight: 600;
|
|
196
504
|
font-size: 13px;
|
|
505
|
+
background: rgba(255,255,255,0.03);
|
|
506
|
+
}
|
|
507
|
+
.toggle-btn:hover { background: rgba(255,255,255,0.08); }
|
|
508
|
+
|
|
509
|
+
/* HEADER */
|
|
510
|
+
.header {
|
|
511
|
+
padding: 12px 16px;
|
|
512
|
+
background: rgba(0,0,0,0.2);
|
|
513
|
+
border-bottom: 1px solid ${COLORS.border};
|
|
514
|
+
display: flex;
|
|
515
|
+
justify-content: space-between;
|
|
516
|
+
align-items: center;
|
|
517
|
+
cursor: move;
|
|
518
|
+
flex-shrink: 0;
|
|
197
519
|
}
|
|
198
520
|
.dot {
|
|
199
521
|
width: 8px;
|
|
200
522
|
height: 8px;
|
|
201
523
|
border-radius: 50%;
|
|
202
524
|
background: ${COLORS.accent};
|
|
203
|
-
|
|
204
|
-
box-shadow: 0 0 10px #00d2ff;
|
|
525
|
+
box-shadow: 0 0 12px ${COLORS.accentColor};
|
|
205
526
|
}
|
|
206
|
-
.
|
|
207
|
-
|
|
208
|
-
|
|
527
|
+
.icon-btn { cursor: pointer; opacity: 0.6; padding: 4px; font-size: 18px; line-height: 1; }
|
|
528
|
+
.icon-btn:hover { opacity: 1; color: white; }
|
|
529
|
+
|
|
530
|
+
/* TABS */
|
|
531
|
+
.tabs {
|
|
532
|
+
display: flex;
|
|
533
|
+
background: rgba(0,0,0,0.2);
|
|
209
534
|
border-bottom: 1px solid ${COLORS.border};
|
|
535
|
+
font-size: 11px;
|
|
536
|
+
font-weight: 600;
|
|
537
|
+
}
|
|
538
|
+
.tab-btn {
|
|
539
|
+
flex: 1;
|
|
540
|
+
padding: 10px;
|
|
541
|
+
text-align: center;
|
|
542
|
+
cursor: pointer;
|
|
543
|
+
opacity: 0.6;
|
|
544
|
+
border-bottom: 2px solid transparent;
|
|
210
545
|
display: flex;
|
|
211
|
-
justify-content: space-between;
|
|
212
546
|
align-items: center;
|
|
213
|
-
|
|
547
|
+
justify-content: center;
|
|
548
|
+
gap: 6px;
|
|
214
549
|
}
|
|
215
|
-
.
|
|
550
|
+
.tab-btn:hover { opacity: 0.9; background: rgba(255,255,255,0.02); }
|
|
551
|
+
.tab-btn.active { opacity: 1; border-bottom-color: ${COLORS.accentColor}; color: ${COLORS.accentColor}; background: rgba(0,210,255,0.05); }
|
|
552
|
+
|
|
553
|
+
/* CONTENT */
|
|
554
|
+
.content {
|
|
216
555
|
flex: 1;
|
|
217
556
|
overflow-y: auto;
|
|
218
|
-
|
|
219
|
-
|
|
557
|
+
overflow-x: hidden;
|
|
558
|
+
min-height: 300px;
|
|
220
559
|
}
|
|
221
|
-
.
|
|
222
|
-
.
|
|
223
|
-
|
|
560
|
+
.content::-webkit-scrollbar { width: 6px; }
|
|
561
|
+
.content::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 3px; }
|
|
562
|
+
|
|
563
|
+
/* INSPECTOR LIST */
|
|
564
|
+
.list { padding: 12px; display: flex; flex-direction: column; gap: 8px; }
|
|
565
|
+
.empty-state { padding: 40px; text-align: center; opacity: 0.4; font-size: 12px; }
|
|
566
|
+
|
|
224
567
|
.unit-card {
|
|
225
|
-
padding: 10px;
|
|
226
|
-
margin-bottom: 10px;
|
|
227
|
-
border-radius: 10px;
|
|
228
568
|
background: ${COLORS.cardBg};
|
|
229
569
|
border: 1px solid ${COLORS.border};
|
|
230
|
-
|
|
570
|
+
border-left: 3px solid transparent;
|
|
571
|
+
border-radius: 6px;
|
|
572
|
+
padding: 10px;
|
|
573
|
+
transition: background 0.1s;
|
|
231
574
|
}
|
|
575
|
+
.unit-card:hover { background: ${COLORS.cardHover}; }
|
|
576
|
+
.source-card { border-left-color: ${COLORS.secondaryText} !important; }
|
|
577
|
+
|
|
232
578
|
.unit-header {
|
|
233
579
|
display: flex;
|
|
234
580
|
justify-content: space-between;
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
.unit-type {
|
|
238
|
-
font-size: 9px;
|
|
239
|
-
opacity: 0.5;
|
|
240
|
-
text-transform: uppercase;
|
|
581
|
+
align-items: center;
|
|
582
|
+
margin-bottom: 8px;
|
|
241
583
|
}
|
|
242
584
|
.status-dot {
|
|
243
585
|
width: 6px;
|
|
244
586
|
height: 6px;
|
|
245
587
|
border-radius: 50%;
|
|
246
|
-
display: inline-block;
|
|
247
|
-
margin-right: 6px;
|
|
248
588
|
}
|
|
249
|
-
.status-ok { background: ${COLORS.success}; box-shadow: 0 0
|
|
250
|
-
.status-fail { background: ${COLORS.error}; box-shadow: 0 0
|
|
251
|
-
.status-pending { background: ${COLORS.pending}; box-shadow: 0 0
|
|
252
|
-
|
|
253
|
-
.
|
|
254
|
-
|
|
589
|
+
.status-ok { background: ${COLORS.success}; box-shadow: 0 0 6px ${COLORS.success}; }
|
|
590
|
+
.status-fail { background: ${COLORS.error}; box-shadow: 0 0 6px ${COLORS.error}; }
|
|
591
|
+
.status-pending { background: ${COLORS.pending}; box-shadow: 0 0 6px ${COLORS.pending}; }
|
|
592
|
+
|
|
593
|
+
.badg { font-size: 8px; font-weight: 700; padding: 2px 4px; border-radius: 3px; background: rgba(255,255,255,0.1); }
|
|
594
|
+
.type-guard { color: ${COLORS.accentColor}; background: rgba(0,210,255,0.1); }
|
|
595
|
+
.type-source { color: ${COLORS.secondaryText}; }
|
|
596
|
+
|
|
597
|
+
.value-row {
|
|
598
|
+
font-size: 11px;
|
|
599
|
+
font-family: monospace;
|
|
600
|
+
background: rgba(0,0,0,0.2);
|
|
601
|
+
padding: 6px 8px;
|
|
602
|
+
border-radius: 4px;
|
|
603
|
+
display: flex;
|
|
604
|
+
align-items: center;
|
|
605
|
+
justify-content: space-between;
|
|
606
|
+
}
|
|
607
|
+
.value-text { color: ${COLORS.accentColor}; }
|
|
608
|
+
.editable-value { cursor: pointer; border: 1px dashed transparent; }
|
|
609
|
+
.editable-value:hover { border-color: rgba(255,255,255,0.2); background: rgba(255,255,255,0.05); }
|
|
610
|
+
.edit-icon { opacity: 0; transition: opacity 0.2s; }
|
|
611
|
+
.editable-value:hover .edit-icon { opacity: 0.7; }
|
|
612
|
+
|
|
613
|
+
.edit-form { margin: 0; padding: 0; width: 100%; }
|
|
614
|
+
.edit-input {
|
|
615
|
+
width: 100%;
|
|
616
|
+
background: ${COLORS.inputBg};
|
|
617
|
+
border: 1px solid ${COLORS.accentColor};
|
|
618
|
+
color: white;
|
|
255
619
|
font-family: monospace;
|
|
256
|
-
|
|
620
|
+
font-size: 11px;
|
|
621
|
+
padding: 6px;
|
|
622
|
+
border-radius: 4px;
|
|
623
|
+
outline: none;
|
|
257
624
|
}
|
|
625
|
+
|
|
258
626
|
.reason {
|
|
627
|
+
margin-top: 8px;
|
|
628
|
+
padding: 8px;
|
|
629
|
+
background: rgba(255, 75, 43, 0.1);
|
|
630
|
+
border: 1px solid rgba(255, 75, 43, 0.2);
|
|
631
|
+
border-radius: 4px;
|
|
259
632
|
color: ${COLORS.error};
|
|
260
|
-
margin-top: 4px;
|
|
261
633
|
font-size: 11px;
|
|
262
|
-
background: rgba(255,75,43,0.1);
|
|
263
|
-
padding: 4px;
|
|
264
|
-
border-radius: 4px;
|
|
265
|
-
}
|
|
266
|
-
.footer {
|
|
267
|
-
padding: 8px 12px;
|
|
268
|
-
font-size: 10px;
|
|
269
|
-
opacity: 0.5;
|
|
270
|
-
text-align: right;
|
|
271
|
-
background: rgba(0,0,0,0.1);
|
|
272
634
|
}
|
|
273
|
-
.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
635
|
+
.pending-reason { color: ${COLORS.pending} !important; background: rgba(253, 187, 45, 0.1); border-color: rgba(253, 187, 45, 0.2); }
|
|
636
|
+
.meta-block { margin: 4px 0 0 0; opacity: 0.7; font-size: 10px; }
|
|
637
|
+
|
|
638
|
+
/* TREE VIEW */
|
|
639
|
+
.tree-view { padding: 12px; font-size: 12px; }
|
|
640
|
+
.tree-node { margin-bottom: 2px; }
|
|
641
|
+
.tree-row {
|
|
642
|
+
display: flex;
|
|
643
|
+
align-items: center;
|
|
644
|
+
padding: 4px 6px;
|
|
645
|
+
border-radius: 4px;
|
|
277
646
|
cursor: pointer;
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
.explanation {
|
|
281
|
-
margin-top: 8px;
|
|
282
|
-
padding-top: 8px;
|
|
283
|
-
border-top: 1px dashed ${COLORS.border};
|
|
647
|
+
user-select: none;
|
|
284
648
|
}
|
|
285
|
-
.
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
649
|
+
.tree-row:hover { background: rgba(255,255,255,0.05); }
|
|
650
|
+
.tree-icon { margin-right: 4px; opacity: 0.5; width: 14px; height: 14px; display: flex; align-items: center; justify-content: center; }
|
|
651
|
+
.tree-name { opacity: 0.9; }
|
|
652
|
+
.mini-badge {
|
|
653
|
+
font-size: 9px; margin-left: auto; padding: 1px 4px; border-radius: 3px;
|
|
654
|
+
font-weight: bold; opacity: 0.6;
|
|
290
655
|
}
|
|
656
|
+
.mini-badge.fail { background: ${COLORS.error}; color: white; opacity: 1; }
|
|
657
|
+
.mini-badge.source { background: rgba(255,255,255,0.1); }
|
|
658
|
+
|
|
291
659
|
</style>
|
|
292
|
-
<div class="container">
|
|
293
|
-
<div class="glass">
|
|
294
|
-
${!this.isOpen ? `
|
|
295
|
-
<div class="toggle-btn" id="toggle">
|
|
296
|
-
<div class="dot"></div>
|
|
297
|
-
Pulse (${this.units.length})
|
|
298
|
-
</div>
|
|
299
|
-
` : `
|
|
300
|
-
<div class="header">
|
|
301
|
-
<div style="display:flex;align-items:center">
|
|
302
|
-
<div class="dot" style="width:10px;height:10px"></div>
|
|
303
|
-
<strong style="font-size:14px">Pulse Inspector</strong>
|
|
304
|
-
</div>
|
|
305
|
-
<div id="close" style="cursor:pointer;font-size:18px;opacity:0.6">\xD7</div>
|
|
306
|
-
</div>
|
|
307
|
-
<div class="list">
|
|
308
|
-
${this.units.length === 0 ? '<div style="text-align:center;padding:20px;opacity:0.5">No units detected</div>' : ""}
|
|
309
|
-
${this.units.map((u) => this.renderUnit(u)).join("")}
|
|
310
|
-
</div>
|
|
311
|
-
<div class="footer">v0.2.0 \u2022 Framework-Agnostic</div>
|
|
312
|
-
`}
|
|
313
|
-
</div>
|
|
314
|
-
</div>
|
|
315
660
|
`;
|
|
316
|
-
this.shadow.getElementById("toggle")?.addEventListener("click", () => this.toggle());
|
|
317
|
-
this.shadow.getElementById("close")?.addEventListener("click", () => this.toggle());
|
|
318
|
-
}
|
|
319
|
-
renderUnit(unit) {
|
|
320
|
-
const isGuard = "state" in unit;
|
|
321
|
-
const name = unit._name || "unnamed";
|
|
322
|
-
if (isGuard) {
|
|
323
|
-
const g = unit;
|
|
324
|
-
const explanation = g.explain();
|
|
325
|
-
const statusClass = `status-${explanation.status}`;
|
|
326
|
-
return `
|
|
327
|
-
<div class="unit-card" style="border-color: ${explanation.status === "fail" ? COLORS.error + "44" : COLORS.border}">
|
|
328
|
-
<div class="unit-header">
|
|
329
|
-
<span>
|
|
330
|
-
<span class="status-dot ${statusClass}"></span>
|
|
331
|
-
<strong>${name}</strong>
|
|
332
|
-
</span>
|
|
333
|
-
<span class="unit-type">Guard</span>
|
|
334
|
-
</div>
|
|
335
|
-
${explanation.status === "ok" ? `<div class="value">Value: ${JSON.stringify(explanation.value)}</div>` : ""}
|
|
336
|
-
${explanation.status === "fail" ? `<div class="reason">${explanation.reason}</div>` : ""}
|
|
337
|
-
${explanation.status === "pending" ? `<div style="opacity:0.5">Evaluating...</div>` : ""}
|
|
338
|
-
|
|
339
|
-
${explanation.dependencies.length > 0 ? `
|
|
340
|
-
<div class="explanation">
|
|
341
|
-
<div style="font-size:9px;opacity:0.5;margin-bottom:4px">DEPENDENCIES</div>
|
|
342
|
-
${explanation.dependencies.map((dep) => `
|
|
343
|
-
<div class="dep-item">
|
|
344
|
-
<span>${dep.name} <small opacity="0.5">(${dep.type})</small></span>
|
|
345
|
-
${dep.status ? `<span class="status-dot status-${dep.status}" style="width:4px;height:4px"></span>` : ""}
|
|
346
|
-
</div>
|
|
347
|
-
`).join("")}
|
|
348
|
-
</div>
|
|
349
|
-
` : ""}
|
|
350
|
-
</div>
|
|
351
|
-
`;
|
|
352
|
-
} else {
|
|
353
|
-
const value = unit();
|
|
354
|
-
return `
|
|
355
|
-
<div class="unit-card">
|
|
356
|
-
<div class="unit-header">
|
|
357
|
-
<span>
|
|
358
|
-
<span class="status-dot status-ok"></span>
|
|
359
|
-
<strong>${name}</strong>
|
|
360
|
-
</span>
|
|
361
|
-
<span class="unit-type">Source</span>
|
|
362
|
-
</div>
|
|
363
|
-
<div class="value">Value: ${JSON.stringify(value)}</div>
|
|
364
|
-
</div>
|
|
365
|
-
`;
|
|
366
|
-
}
|
|
367
661
|
}
|
|
368
662
|
};
|
|
369
663
|
if (!customElements.get("pulse-inspector")) {
|