@pulse-js/tools 0.1.4 → 0.1.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 +5 -6
- package/dist/index.cjs +492 -202
- package/dist/index.d.cts +21 -8
- package/dist/index.d.ts +21 -8
- package/dist/index.js +492 -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,543 @@ 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
|
+
if (unit && !("state" in unit)) {
|
|
208
|
+
this.editingUnit = unit;
|
|
209
|
+
this.editValue = JSON.stringify(unit());
|
|
210
|
+
this.render();
|
|
211
|
+
requestAnimationFrame(() => {
|
|
212
|
+
const input = this.shadow.querySelector(".edit-input");
|
|
213
|
+
if (input) {
|
|
214
|
+
input.focus();
|
|
215
|
+
input.select();
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (this.editingUnit && !target.closest(".edit-form")) {
|
|
222
|
+
this.editingUnit = null;
|
|
223
|
+
this.render();
|
|
224
|
+
}
|
|
225
|
+
const treeNode = target.closest(".tree-toggle");
|
|
226
|
+
if (treeNode) {
|
|
227
|
+
e.stopPropagation();
|
|
228
|
+
const id = treeNode.getAttribute("data-id");
|
|
229
|
+
if (id) {
|
|
230
|
+
if (this.state.expandedNodes.includes(id)) {
|
|
231
|
+
this.state.expandedNodes = this.state.expandedNodes.filter((n) => n !== id);
|
|
232
|
+
} else {
|
|
233
|
+
this.state.expandedNodes.push(id);
|
|
234
|
+
}
|
|
235
|
+
this.saveState();
|
|
236
|
+
this.render();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
handleEditSubmit = (e) => {
|
|
241
|
+
e.preventDefault();
|
|
242
|
+
if (!this.editingUnit) return;
|
|
243
|
+
try {
|
|
244
|
+
const form = e.target;
|
|
245
|
+
const input = form.querySelector("input");
|
|
246
|
+
let val = input.value;
|
|
247
|
+
try {
|
|
248
|
+
if (val === "true") val = true;
|
|
249
|
+
else if (val === "false") val = false;
|
|
250
|
+
else if (val === "null") val = null;
|
|
251
|
+
else if (val === "undefined") val = void 0;
|
|
252
|
+
else if (!isNaN(Number(val)) && val.trim() !== "") val = Number(val);
|
|
253
|
+
else if (val.startsWith("{") || val.startsWith("[")) val = JSON.parse(val);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
}
|
|
256
|
+
this.editingUnit.set(val);
|
|
257
|
+
this.editingUnit = null;
|
|
258
|
+
this.render();
|
|
259
|
+
} catch (err) {
|
|
260
|
+
console.error("Pulse DevTools: Failed to update value", err);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
handleEditKeydown = (e) => {
|
|
264
|
+
if (this.editingUnit && e.key === "Escape") {
|
|
265
|
+
this.editingUnit = null;
|
|
266
|
+
this.render();
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
// --- RENDERERS ---
|
|
158
270
|
render() {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
271
|
+
if (!this.state.isOpen) {
|
|
272
|
+
this.shadow.innerHTML = this.getStyles() + `
|
|
273
|
+
<div class="container" style="left: ${this.state.pos.x}px; top: ${this.state.pos.y}px">
|
|
274
|
+
<div class="glass" style="border-radius:30px; width:auto;">
|
|
275
|
+
<div class="toggle-btn" id="toggle">
|
|
276
|
+
<div class="dot"></div>
|
|
277
|
+
Pulse (${this.units.length})
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
`;
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
this.shadow.innerHTML = this.getStyles() + `
|
|
285
|
+
<div class="container" style="left: ${this.state.pos.x}px; top: ${this.state.pos.y}px">
|
|
286
|
+
<div class="glass">
|
|
287
|
+
|
|
288
|
+
<div class="header">
|
|
289
|
+
<div style="display:flex;align-items:center;gap:10px">
|
|
290
|
+
<div class="dot" style="width:10px;height:10px"></div>
|
|
291
|
+
<strong style="font-size:14px;letter-spacing:0.5px">PULSE</strong>
|
|
292
|
+
<span style="font-size:10px;opacity:0.5;margin-top:2px">v0.2.0</span>
|
|
293
|
+
</div>
|
|
294
|
+
<div id="close" class="icon-btn">\xD7</div>
|
|
295
|
+
</div>
|
|
296
|
+
|
|
297
|
+
<div class="tabs">
|
|
298
|
+
<div class="tab-btn ${this.state.activeTab === "inspector" ? "active" : ""}" data-tab="inspector">
|
|
299
|
+
${ICONS.list} Inspector
|
|
300
|
+
</div>
|
|
301
|
+
<div class="tab-btn ${this.state.activeTab === "tree" ? "active" : ""}" data-tab="tree">
|
|
302
|
+
${ICONS.tree} Pulse Tree
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
<div class="content">
|
|
307
|
+
${this.state.activeTab === "inspector" ? this.renderInspector() : this.renderTree()}
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
`;
|
|
313
|
+
}
|
|
314
|
+
renderInspector() {
|
|
315
|
+
if (this.units.length === 0) {
|
|
316
|
+
return `<div class="empty-state">No Pulse units detected.</div>`;
|
|
317
|
+
}
|
|
318
|
+
return `
|
|
319
|
+
<div class="list">
|
|
320
|
+
${this.units.map((u) => this.renderUnitCard(u)).join("")}
|
|
321
|
+
</div>
|
|
322
|
+
`;
|
|
323
|
+
}
|
|
324
|
+
renderUnitCard(unit) {
|
|
325
|
+
const isGuard = "state" in unit;
|
|
326
|
+
const name = unit._name || "unnamed";
|
|
327
|
+
const explanation = isGuard ? unit.explain() : null;
|
|
328
|
+
const value = isGuard ? explanation.value : unit();
|
|
329
|
+
let status = "ok";
|
|
330
|
+
if (isGuard) status = explanation.status;
|
|
331
|
+
const isEditing = this.editingUnit === unit;
|
|
332
|
+
return `
|
|
333
|
+
<div class="unit-card ${isGuard ? "" : "source-card"}" style="border-left-color: ${this.getStatusColor(status)}">
|
|
334
|
+
<div class="unit-header">
|
|
335
|
+
<div style="display:flex;align-items:center;gap:6px">
|
|
336
|
+
<div class="status-dot status-${status}"></div>
|
|
337
|
+
<strong title="${name}">${name}</strong>
|
|
338
|
+
</div>
|
|
339
|
+
<span class="badg type-${isGuard ? "guard" : "source"}">${isGuard ? "GUARD" : "SOURCE"}</span>
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
<div class="unit-body">
|
|
343
|
+
${isEditing ? `
|
|
344
|
+
<form class="edit-form">
|
|
345
|
+
<input class="edit-input" value='${this.safeStringify(value)}' />
|
|
346
|
+
</form>
|
|
347
|
+
` : `
|
|
348
|
+
<div class="value-row ${!isGuard ? "editable-value" : ""}" data-name="${name}" title="${!isGuard ? "Click to edit" : ""}">
|
|
349
|
+
<span style="opacity:0.5;margin-right:6px">Value:</span>
|
|
350
|
+
<span class="value-text">${this.formatValue(value)}</span>
|
|
351
|
+
${!isGuard ? `<span class="edit-icon">${ICONS.edit}</span>` : ""}
|
|
352
|
+
</div>
|
|
353
|
+
`}
|
|
354
|
+
|
|
355
|
+
${isGuard && explanation.status === "fail" ? this.renderReason(explanation.reason) : ""}
|
|
356
|
+
${isGuard && explanation.status === "pending" ? `
|
|
357
|
+
<div class="reason pending-reason">
|
|
358
|
+
\u23F3 Evaluating... ${explanation.lastReason ? `<br/><span style="opacity:0.7;font-size:9px">Last error: ${this.formatReasonText(explanation.lastReason)}</span>` : ""}
|
|
359
|
+
</div>
|
|
360
|
+
` : ""}
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
`;
|
|
364
|
+
}
|
|
365
|
+
renderTree() {
|
|
366
|
+
const guards = this.units.filter((u) => "state" in u);
|
|
367
|
+
if (guards.length === 0) return `<div class="empty-state">No Guards to visualize.</div>`;
|
|
368
|
+
return `
|
|
369
|
+
<div class="tree-view">
|
|
370
|
+
${guards.map((g) => this.renderTreeNode(g, 0, g._name)).join("")}
|
|
371
|
+
</div>
|
|
372
|
+
`;
|
|
373
|
+
}
|
|
374
|
+
renderTreeNode(guard, depth, key) {
|
|
375
|
+
const isExpanded = this.state.expandedNodes.includes(key);
|
|
376
|
+
const explanation = guard.explain();
|
|
377
|
+
const deps = explanation.dependencies || [];
|
|
378
|
+
const hasDeps = deps.length > 0;
|
|
379
|
+
const statusColor = this.getStatusColor(explanation.status);
|
|
380
|
+
return `
|
|
381
|
+
<div class="tree-node" style="padding-left:${depth * 12}px">
|
|
382
|
+
<div class="tree-row tree-toggle" data-id="${key}">
|
|
383
|
+
<span class="tree-icon" style="visibility:${hasDeps ? "visible" : "hidden"}">
|
|
384
|
+
${isExpanded ? ICONS.chevronDown : ICONS.chevronRight}
|
|
385
|
+
</span>
|
|
386
|
+
<span class="status-dot status-${explanation.status}" style="width:6px;height:6px;margin:0 6px"></span>
|
|
387
|
+
<span class="tree-name" style="color:${explanation.status === "fail" ? COLORS.error : "inherit"}">
|
|
388
|
+
${explanation.name}
|
|
389
|
+
</span>
|
|
390
|
+
${explanation.status === "fail" ? `
|
|
391
|
+
<span class="mini-badge fail">!</span>
|
|
392
|
+
` : ""}
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
${isExpanded && hasDeps ? `
|
|
396
|
+
<div class="tree-children">
|
|
397
|
+
${deps.map((dep) => {
|
|
398
|
+
const unit = this.units.find((u) => u._name === dep.name);
|
|
399
|
+
const childKey = key + "-" + dep.name;
|
|
400
|
+
if (unit && "state" in unit) {
|
|
401
|
+
return this.renderTreeNode(unit, depth + 1, childKey);
|
|
402
|
+
} else {
|
|
403
|
+
return `
|
|
404
|
+
<div class="tree-node" style="padding-left:${(depth + 1) * 12}px">
|
|
405
|
+
<div class="tree-row">
|
|
406
|
+
<span class="tree-icon" style="visibility:hidden"></span>
|
|
407
|
+
<span class="status-dot status-ok" style="background:${COLORS.secondaryText}"></span>
|
|
408
|
+
<span class="tree-name" style="opacity:0.7">${dep.name}</span>
|
|
409
|
+
<span class="mini-badge source">S</span>
|
|
410
|
+
</div>
|
|
411
|
+
</div>
|
|
412
|
+
`;
|
|
413
|
+
}
|
|
414
|
+
}).join("")}
|
|
415
|
+
</div>
|
|
416
|
+
` : ""}
|
|
417
|
+
`;
|
|
418
|
+
}
|
|
419
|
+
// --- HELPERS ---
|
|
420
|
+
renderReason(reason) {
|
|
421
|
+
if (!reason) return "";
|
|
422
|
+
const text = this.formatReasonText(reason);
|
|
423
|
+
const meta = typeof reason === "object" && reason.meta ? reason.meta : null;
|
|
424
|
+
return `
|
|
425
|
+
<div class="reason">
|
|
426
|
+
<strong>${typeof reason === "object" ? reason.code || "ERROR" : "FAIL"}</strong>
|
|
427
|
+
<div>${text}</div>
|
|
428
|
+
${meta ? `<pre class="meta-block">${JSON.stringify(meta, null, 2)}</pre>` : ""}
|
|
429
|
+
</div>
|
|
430
|
+
`;
|
|
431
|
+
}
|
|
432
|
+
formatReasonText(reason) {
|
|
433
|
+
if (typeof reason === "string") return reason;
|
|
434
|
+
return reason.message || JSON.stringify(reason);
|
|
435
|
+
}
|
|
436
|
+
formatValue(val) {
|
|
437
|
+
if (val === void 0) return "undefined";
|
|
438
|
+
if (val === null) return "null";
|
|
439
|
+
if (typeof val === "function") return "\u0192()";
|
|
440
|
+
if (typeof val === "object") return "{...}";
|
|
441
|
+
return String(val);
|
|
442
|
+
}
|
|
443
|
+
safeStringify(val) {
|
|
444
|
+
try {
|
|
445
|
+
if (val === void 0) return "undefined";
|
|
446
|
+
return JSON.stringify(val);
|
|
447
|
+
} catch {
|
|
448
|
+
return String(val);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
getStatusColor(status) {
|
|
452
|
+
switch (status) {
|
|
453
|
+
case "ok":
|
|
454
|
+
return COLORS.success;
|
|
455
|
+
case "fail":
|
|
456
|
+
return COLORS.error;
|
|
457
|
+
case "pending":
|
|
458
|
+
return COLORS.pending;
|
|
459
|
+
default:
|
|
460
|
+
return COLORS.secondaryText;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
getStyles() {
|
|
464
|
+
return `
|
|
162
465
|
<style>
|
|
163
|
-
:host {
|
|
164
|
-
|
|
165
|
-
|
|
466
|
+
:host { all: initial; font-family: 'Inter', -apple-system, sans-serif; }
|
|
467
|
+
* { box-sizing: border-box; }
|
|
468
|
+
|
|
166
469
|
.container {
|
|
167
470
|
position: fixed;
|
|
168
|
-
|
|
169
|
-
top: ${this.position.y}px;
|
|
170
|
-
width: ${w}px;
|
|
471
|
+
height: auto;
|
|
171
472
|
z-index: 999999;
|
|
172
|
-
|
|
173
|
-
color:
|
|
174
|
-
user-select: none;
|
|
473
|
+
filter: drop-shadow(0 8px 32px rgba(0,0,0,0.5));
|
|
474
|
+
color: ${COLORS.text};
|
|
175
475
|
}
|
|
476
|
+
|
|
176
477
|
.glass {
|
|
177
478
|
background: ${COLORS.bg};
|
|
178
|
-
backdrop-filter: blur(
|
|
179
|
-
-webkit-backdrop-filter: blur(20px);
|
|
479
|
+
backdrop-filter: blur(12px);
|
|
180
480
|
border: 1px solid ${COLORS.border};
|
|
181
|
-
border-radius:
|
|
182
|
-
box-shadow: 0 10px 40px rgba(0,0,0,0.5);
|
|
481
|
+
border-radius: 12px;
|
|
183
482
|
overflow: hidden;
|
|
184
|
-
transition: border-radius 0.2s ease;
|
|
185
483
|
display: flex;
|
|
186
484
|
flex-direction: column;
|
|
485
|
+
width: 400px;
|
|
486
|
+
max-height: 80vh;
|
|
487
|
+
transition: width 0.2s, height 0.2s;
|
|
187
488
|
}
|
|
489
|
+
|
|
490
|
+
/* TOGGLE BUTTON MODE */
|
|
188
491
|
.toggle-btn {
|
|
189
|
-
width:
|
|
190
|
-
height:
|
|
492
|
+
width: 140px;
|
|
493
|
+
height: 48px;
|
|
191
494
|
display: flex;
|
|
192
495
|
align-items: center;
|
|
193
496
|
justify-content: center;
|
|
194
|
-
|
|
497
|
+
gap: 10px;
|
|
498
|
+
cursor: pointer;
|
|
195
499
|
font-weight: 600;
|
|
196
500
|
font-size: 13px;
|
|
501
|
+
background: rgba(255,255,255,0.03);
|
|
502
|
+
}
|
|
503
|
+
.toggle-btn:hover { background: rgba(255,255,255,0.08); }
|
|
504
|
+
|
|
505
|
+
/* HEADER */
|
|
506
|
+
.header {
|
|
507
|
+
padding: 12px 16px;
|
|
508
|
+
background: rgba(0,0,0,0.2);
|
|
509
|
+
border-bottom: 1px solid ${COLORS.border};
|
|
510
|
+
display: flex;
|
|
511
|
+
justify-content: space-between;
|
|
512
|
+
align-items: center;
|
|
513
|
+
cursor: move;
|
|
514
|
+
flex-shrink: 0;
|
|
197
515
|
}
|
|
198
516
|
.dot {
|
|
199
517
|
width: 8px;
|
|
200
518
|
height: 8px;
|
|
201
519
|
border-radius: 50%;
|
|
202
520
|
background: ${COLORS.accent};
|
|
203
|
-
|
|
204
|
-
box-shadow: 0 0 10px #00d2ff;
|
|
521
|
+
box-shadow: 0 0 12px ${COLORS.accentColor};
|
|
205
522
|
}
|
|
206
|
-
.
|
|
207
|
-
|
|
208
|
-
|
|
523
|
+
.icon-btn { cursor: pointer; opacity: 0.6; padding: 4px; font-size: 18px; line-height: 1; }
|
|
524
|
+
.icon-btn:hover { opacity: 1; color: white; }
|
|
525
|
+
|
|
526
|
+
/* TABS */
|
|
527
|
+
.tabs {
|
|
528
|
+
display: flex;
|
|
529
|
+
background: rgba(0,0,0,0.2);
|
|
209
530
|
border-bottom: 1px solid ${COLORS.border};
|
|
531
|
+
font-size: 11px;
|
|
532
|
+
font-weight: 600;
|
|
533
|
+
}
|
|
534
|
+
.tab-btn {
|
|
535
|
+
flex: 1;
|
|
536
|
+
padding: 10px;
|
|
537
|
+
text-align: center;
|
|
538
|
+
cursor: pointer;
|
|
539
|
+
opacity: 0.6;
|
|
540
|
+
border-bottom: 2px solid transparent;
|
|
210
541
|
display: flex;
|
|
211
|
-
justify-content: space-between;
|
|
212
542
|
align-items: center;
|
|
213
|
-
|
|
543
|
+
justify-content: center;
|
|
544
|
+
gap: 6px;
|
|
214
545
|
}
|
|
215
|
-
.
|
|
546
|
+
.tab-btn:hover { opacity: 0.9; background: rgba(255,255,255,0.02); }
|
|
547
|
+
.tab-btn.active { opacity: 1; border-bottom-color: ${COLORS.accentColor}; color: ${COLORS.accentColor}; background: rgba(0,210,255,0.05); }
|
|
548
|
+
|
|
549
|
+
/* CONTENT */
|
|
550
|
+
.content {
|
|
216
551
|
flex: 1;
|
|
217
552
|
overflow-y: auto;
|
|
218
|
-
|
|
219
|
-
|
|
553
|
+
overflow-x: hidden;
|
|
554
|
+
min-height: 300px;
|
|
220
555
|
}
|
|
221
|
-
.
|
|
222
|
-
.
|
|
223
|
-
|
|
556
|
+
.content::-webkit-scrollbar { width: 6px; }
|
|
557
|
+
.content::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 3px; }
|
|
558
|
+
|
|
559
|
+
/* INSPECTOR LIST */
|
|
560
|
+
.list { padding: 12px; display: flex; flex-direction: column; gap: 8px; }
|
|
561
|
+
.empty-state { padding: 40px; text-align: center; opacity: 0.4; font-size: 12px; }
|
|
562
|
+
|
|
224
563
|
.unit-card {
|
|
225
|
-
padding: 10px;
|
|
226
|
-
margin-bottom: 10px;
|
|
227
|
-
border-radius: 10px;
|
|
228
564
|
background: ${COLORS.cardBg};
|
|
229
565
|
border: 1px solid ${COLORS.border};
|
|
230
|
-
|
|
566
|
+
border-left: 3px solid transparent;
|
|
567
|
+
border-radius: 6px;
|
|
568
|
+
padding: 10px;
|
|
569
|
+
transition: background 0.1s;
|
|
231
570
|
}
|
|
571
|
+
.unit-card:hover { background: ${COLORS.cardHover}; }
|
|
572
|
+
.source-card { border-left-color: ${COLORS.secondaryText} !important; }
|
|
573
|
+
|
|
232
574
|
.unit-header {
|
|
233
575
|
display: flex;
|
|
234
576
|
justify-content: space-between;
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
.unit-type {
|
|
238
|
-
font-size: 9px;
|
|
239
|
-
opacity: 0.5;
|
|
240
|
-
text-transform: uppercase;
|
|
577
|
+
align-items: center;
|
|
578
|
+
margin-bottom: 8px;
|
|
241
579
|
}
|
|
242
580
|
.status-dot {
|
|
243
581
|
width: 6px;
|
|
244
582
|
height: 6px;
|
|
245
583
|
border-radius: 50%;
|
|
246
|
-
display: inline-block;
|
|
247
|
-
margin-right: 6px;
|
|
248
584
|
}
|
|
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
|
-
|
|
585
|
+
.status-ok { background: ${COLORS.success}; box-shadow: 0 0 6px ${COLORS.success}; }
|
|
586
|
+
.status-fail { background: ${COLORS.error}; box-shadow: 0 0 6px ${COLORS.error}; }
|
|
587
|
+
.status-pending { background: ${COLORS.pending}; box-shadow: 0 0 6px ${COLORS.pending}; }
|
|
588
|
+
|
|
589
|
+
.badg { font-size: 8px; font-weight: 700; padding: 2px 4px; border-radius: 3px; background: rgba(255,255,255,0.1); }
|
|
590
|
+
.type-guard { color: ${COLORS.accentColor}; background: rgba(0,210,255,0.1); }
|
|
591
|
+
.type-source { color: ${COLORS.secondaryText}; }
|
|
592
|
+
|
|
593
|
+
.value-row {
|
|
594
|
+
font-size: 11px;
|
|
595
|
+
font-family: monospace;
|
|
596
|
+
background: rgba(0,0,0,0.2);
|
|
597
|
+
padding: 6px 8px;
|
|
598
|
+
border-radius: 4px;
|
|
599
|
+
display: flex;
|
|
600
|
+
align-items: center;
|
|
601
|
+
justify-content: space-between;
|
|
602
|
+
}
|
|
603
|
+
.value-text { color: ${COLORS.accentColor}; }
|
|
604
|
+
.editable-value { cursor: pointer; border: 1px dashed transparent; }
|
|
605
|
+
.editable-value:hover { border-color: rgba(255,255,255,0.2); background: rgba(255,255,255,0.05); }
|
|
606
|
+
.edit-icon { opacity: 0; transition: opacity 0.2s; }
|
|
607
|
+
.editable-value:hover .edit-icon { opacity: 0.7; }
|
|
608
|
+
|
|
609
|
+
.edit-form { margin: 0; padding: 0; width: 100%; }
|
|
610
|
+
.edit-input {
|
|
611
|
+
width: 100%;
|
|
612
|
+
background: ${COLORS.inputBg};
|
|
613
|
+
border: 1px solid ${COLORS.accentColor};
|
|
614
|
+
color: white;
|
|
255
615
|
font-family: monospace;
|
|
256
|
-
|
|
616
|
+
font-size: 11px;
|
|
617
|
+
padding: 6px;
|
|
618
|
+
border-radius: 4px;
|
|
619
|
+
outline: none;
|
|
257
620
|
}
|
|
621
|
+
|
|
258
622
|
.reason {
|
|
623
|
+
margin-top: 8px;
|
|
624
|
+
padding: 8px;
|
|
625
|
+
background: rgba(255, 75, 43, 0.1);
|
|
626
|
+
border: 1px solid rgba(255, 75, 43, 0.2);
|
|
627
|
+
border-radius: 4px;
|
|
259
628
|
color: ${COLORS.error};
|
|
260
|
-
margin-top: 4px;
|
|
261
629
|
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
630
|
}
|
|
273
|
-
.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
631
|
+
.pending-reason { color: ${COLORS.pending} !important; background: rgba(253, 187, 45, 0.1); border-color: rgba(253, 187, 45, 0.2); }
|
|
632
|
+
.meta-block { margin: 4px 0 0 0; opacity: 0.7; font-size: 10px; }
|
|
633
|
+
|
|
634
|
+
/* TREE VIEW */
|
|
635
|
+
.tree-view { padding: 12px; font-size: 12px; }
|
|
636
|
+
.tree-node { margin-bottom: 2px; }
|
|
637
|
+
.tree-row {
|
|
638
|
+
display: flex;
|
|
639
|
+
align-items: center;
|
|
640
|
+
padding: 4px 6px;
|
|
641
|
+
border-radius: 4px;
|
|
277
642
|
cursor: pointer;
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
.explanation {
|
|
281
|
-
margin-top: 8px;
|
|
282
|
-
padding-top: 8px;
|
|
283
|
-
border-top: 1px dashed ${COLORS.border};
|
|
643
|
+
user-select: none;
|
|
284
644
|
}
|
|
285
|
-
.
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
645
|
+
.tree-row:hover { background: rgba(255,255,255,0.05); }
|
|
646
|
+
.tree-icon { margin-right: 4px; opacity: 0.5; width: 14px; height: 14px; display: flex; align-items: center; justify-content: center; }
|
|
647
|
+
.tree-name { opacity: 0.9; }
|
|
648
|
+
.mini-badge {
|
|
649
|
+
font-size: 9px; margin-left: auto; padding: 1px 4px; border-radius: 3px;
|
|
650
|
+
font-weight: bold; opacity: 0.6;
|
|
290
651
|
}
|
|
652
|
+
.mini-badge.fail { background: ${COLORS.error}; color: white; opacity: 1; }
|
|
653
|
+
.mini-badge.source { background: rgba(255,255,255,0.1); }
|
|
654
|
+
|
|
291
655
|
</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
656
|
`;
|
|
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
657
|
}
|
|
368
658
|
};
|
|
369
659
|
if (!customElements.get("pulse-inspector")) {
|