@pulse-js/tools 0.2.0 → 0.3.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/dist/index.cjs +309 -495
- package/dist/index.d.cts +82 -20
- package/dist/index.d.ts +82 -20
- package/dist/index.js +306 -494
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,145 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/agent.ts
|
|
2
2
|
import { PulseRegistry } from "@pulse-js/core";
|
|
3
|
+
var PulseAgent = class _PulseAgent {
|
|
4
|
+
static instance;
|
|
5
|
+
listeners = /* @__PURE__ */ new Set();
|
|
6
|
+
registryUnsubscribe = null;
|
|
7
|
+
unsubs = /* @__PURE__ */ new Map();
|
|
8
|
+
broadcastTimer = null;
|
|
9
|
+
constructor() {
|
|
10
|
+
this.setupRegistryObserver();
|
|
11
|
+
}
|
|
12
|
+
static getInstance() {
|
|
13
|
+
if (!this.instance) {
|
|
14
|
+
this.instance = new _PulseAgent();
|
|
15
|
+
}
|
|
16
|
+
return this.instance;
|
|
17
|
+
}
|
|
18
|
+
setupRegistryObserver() {
|
|
19
|
+
this.syncSubscriptions();
|
|
20
|
+
this.registryUnsubscribe = PulseRegistry.onRegister(() => {
|
|
21
|
+
this.syncSubscriptions();
|
|
22
|
+
this.broadcastStateDebounced();
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Synchronizes subscriptions with the registry.
|
|
27
|
+
*/
|
|
28
|
+
syncSubscriptions() {
|
|
29
|
+
const units = PulseRegistry.getAllWithMeta();
|
|
30
|
+
units.forEach(({ unit, uid }) => {
|
|
31
|
+
if (!this.unsubs.has(uid)) {
|
|
32
|
+
const subscribeFn = unit.subscribe || unit.$subscribe;
|
|
33
|
+
if (typeof subscribeFn === "function") {
|
|
34
|
+
const unsub = subscribeFn.call(unit, () => {
|
|
35
|
+
this.broadcastStateDebounced();
|
|
36
|
+
});
|
|
37
|
+
this.unsubs.set(uid, unsub);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Debounced broadcast to avoid rapid-fire messages.
|
|
44
|
+
*/
|
|
45
|
+
broadcastStateDebounced() {
|
|
46
|
+
if (this.broadcastTimer) clearTimeout(this.broadcastTimer);
|
|
47
|
+
this.broadcastTimer = setTimeout(() => {
|
|
48
|
+
this.broadcastState();
|
|
49
|
+
this.broadcastTimer = null;
|
|
50
|
+
}, 50);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Broadcasts the current state of all registered units.
|
|
54
|
+
*/
|
|
55
|
+
broadcastState() {
|
|
56
|
+
const units = PulseRegistry.getAllWithMeta();
|
|
57
|
+
const payload = units.map(({ unit, uid }) => {
|
|
58
|
+
const isGuard = unit.state;
|
|
59
|
+
const state = isGuard ? unit.explain() : { value: unit() };
|
|
60
|
+
return this.serialize({
|
|
61
|
+
uid,
|
|
62
|
+
name: unit._name || unit.name || "unnamed",
|
|
63
|
+
type: isGuard ? "guard" : "source",
|
|
64
|
+
...state
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
this.emit({
|
|
68
|
+
type: "STATE_UPDATE",
|
|
69
|
+
payload
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Recursively strips non-cloneable values (functions) from an object.
|
|
74
|
+
*/
|
|
75
|
+
serialize(obj) {
|
|
76
|
+
if (obj === null || typeof obj !== "object") {
|
|
77
|
+
return typeof obj === "function" ? void 0 : obj;
|
|
78
|
+
}
|
|
79
|
+
if (Array.isArray(obj)) {
|
|
80
|
+
return obj.map((item) => this.serialize(item));
|
|
81
|
+
}
|
|
82
|
+
const cleaned = {};
|
|
83
|
+
for (const key in obj) {
|
|
84
|
+
const val = this.serialize(obj[key]);
|
|
85
|
+
if (val !== void 0) {
|
|
86
|
+
cleaned[key] = val;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return cleaned;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Emits an event to any connected clients.
|
|
93
|
+
*/
|
|
94
|
+
emit(event) {
|
|
95
|
+
if (typeof window !== "undefined") {
|
|
96
|
+
try {
|
|
97
|
+
window.postMessage({ source: "pulse-agent", ...event }, "*");
|
|
98
|
+
} catch (e) {
|
|
99
|
+
console.error("[Pulse Agent] Failed to emit state update:", e);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
this.listeners.forEach((l) => l(event));
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Connect a local listener (useful for the same-page UI).
|
|
106
|
+
*/
|
|
107
|
+
onEvent(listener) {
|
|
108
|
+
this.listeners.add(listener);
|
|
109
|
+
return () => this.listeners.delete(listener);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Handles an action from the DevTools UI.
|
|
113
|
+
*/
|
|
114
|
+
handleAction(action) {
|
|
115
|
+
const unit = action.uid ? PulseRegistry.get(action.uid) : null;
|
|
116
|
+
if (!unit) return;
|
|
117
|
+
switch (action.type) {
|
|
118
|
+
case "RESET_GUARD":
|
|
119
|
+
if (unit._evaluate) unit._evaluate();
|
|
120
|
+
break;
|
|
121
|
+
case "SET_SOURCE_VALUE":
|
|
122
|
+
if (unit.set) unit.set(action.payload);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
dispose() {
|
|
127
|
+
if (this.registryUnsubscribe) this.registryUnsubscribe();
|
|
128
|
+
this.unsubs.forEach((unsub) => unsub());
|
|
129
|
+
this.unsubs.clear();
|
|
130
|
+
this.listeners.clear();
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
if (typeof window !== "undefined" && window.process?.env?.NODE_ENV !== "production") {
|
|
134
|
+
PulseAgent.getInstance();
|
|
135
|
+
window.addEventListener("message", (event) => {
|
|
136
|
+
if (event.data?.source === "pulse-ui") {
|
|
137
|
+
PulseAgent.getInstance().handleAction(event.data.action);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/inspector.ts
|
|
3
143
|
var COLORS = {
|
|
4
144
|
bg: "rgba(13, 13, 18, 0.96)",
|
|
5
145
|
border: "rgba(255, 255, 255, 0.1)",
|
|
@@ -14,14 +154,6 @@ var COLORS = {
|
|
|
14
154
|
cardHover: "rgba(255, 255, 255, 0.08)",
|
|
15
155
|
inputBg: "rgba(0,0,0,0.3)"
|
|
16
156
|
};
|
|
17
|
-
var ICONS = {
|
|
18
|
-
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>`,
|
|
19
|
-
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>`,
|
|
20
|
-
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>`,
|
|
21
|
-
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>`,
|
|
22
|
-
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>`,
|
|
23
|
-
refresh: `<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.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"/></svg>`
|
|
24
|
-
};
|
|
25
157
|
var STORAGE_KEY = "pulse-devtools-state";
|
|
26
158
|
var PulseInspector = class extends HTMLElement {
|
|
27
159
|
shadow;
|
|
@@ -29,49 +161,29 @@ var PulseInspector = class extends HTMLElement {
|
|
|
29
161
|
// State
|
|
30
162
|
state = {
|
|
31
163
|
pos: { x: window.innerWidth - 360, y: 20 },
|
|
32
|
-
isOpen: true
|
|
33
|
-
activeTab: "inspector",
|
|
34
|
-
expandedNodes: []
|
|
164
|
+
isOpen: true
|
|
35
165
|
};
|
|
36
166
|
isDragging = false;
|
|
37
167
|
offset = { x: 0, y: 0 };
|
|
38
|
-
|
|
39
|
-
editValue = "";
|
|
40
|
-
unsubscribeRegistry;
|
|
41
|
-
unitSubscriptions = /* @__PURE__ */ new Map();
|
|
42
|
-
shortcut = "Ctrl+M";
|
|
43
|
-
// Default shortcut
|
|
168
|
+
totalDragDistance = 0;
|
|
44
169
|
constructor() {
|
|
45
170
|
super();
|
|
46
171
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
47
172
|
this.loadState();
|
|
48
|
-
const shortcutAttr = this.getAttribute("shortcut");
|
|
49
|
-
if (shortcutAttr) {
|
|
50
|
-
this.shortcut = shortcutAttr;
|
|
51
|
-
}
|
|
52
173
|
}
|
|
53
174
|
connectedCallback() {
|
|
175
|
+
this.setupAgentConnection();
|
|
176
|
+
this.setupGlobalListeners();
|
|
54
177
|
this.render();
|
|
55
|
-
this.setupListeners();
|
|
56
|
-
this.refreshUnits();
|
|
57
|
-
this.unsubscribeRegistry = PulseRegistry.onRegister(() => {
|
|
58
|
-
this.refreshUnits();
|
|
59
|
-
});
|
|
60
|
-
window.addEventListener("keydown", this.handleKeyDown);
|
|
61
|
-
window.addEventListener("resize", this.handleResize);
|
|
62
178
|
}
|
|
63
179
|
disconnectedCallback() {
|
|
64
|
-
if (this.unsubscribeRegistry) this.unsubscribeRegistry();
|
|
65
|
-
this.unitSubscriptions.forEach((unsub) => unsub());
|
|
66
|
-
window.removeEventListener("keydown", this.handleKeyDown);
|
|
67
180
|
window.removeEventListener("resize", this.handleResize);
|
|
68
181
|
}
|
|
69
182
|
loadState() {
|
|
70
183
|
try {
|
|
71
184
|
const saved = localStorage.getItem(STORAGE_KEY);
|
|
72
185
|
if (saved) {
|
|
73
|
-
|
|
74
|
-
this.state = { ...this.state, ...parsed };
|
|
186
|
+
this.state = { ...this.state, ...JSON.parse(saved) };
|
|
75
187
|
}
|
|
76
188
|
} catch (e) {
|
|
77
189
|
}
|
|
@@ -82,72 +194,47 @@ var PulseInspector = class extends HTMLElement {
|
|
|
82
194
|
} catch (e) {
|
|
83
195
|
}
|
|
84
196
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
updateUnitSubscriptions() {
|
|
91
|
-
this.unitSubscriptions.forEach((unsub) => unsub());
|
|
92
|
-
this.unitSubscriptions.clear();
|
|
93
|
-
this.units.forEach((unit) => {
|
|
94
|
-
const unsub = unit.subscribe(() => {
|
|
197
|
+
setupAgentConnection() {
|
|
198
|
+
const agent = PulseAgent.getInstance();
|
|
199
|
+
agent.onEvent((event) => {
|
|
200
|
+
if (event.type === "STATE_UPDATE") {
|
|
201
|
+
this.units = event.payload;
|
|
95
202
|
this.render();
|
|
96
|
-
}
|
|
97
|
-
this.unitSubscriptions.set(unit, unsub);
|
|
203
|
+
}
|
|
98
204
|
});
|
|
205
|
+
agent.broadcastState();
|
|
99
206
|
}
|
|
100
|
-
|
|
207
|
+
setupGlobalListeners() {
|
|
101
208
|
this.shadow.addEventListener("mousedown", this.handleMouseDown);
|
|
102
209
|
this.shadow.addEventListener("click", this.handleClick);
|
|
103
|
-
|
|
104
|
-
this.shadow.addEventListener("keydown", this.handleEditKeydown);
|
|
210
|
+
window.addEventListener("resize", this.handleResize);
|
|
105
211
|
}
|
|
106
|
-
handleKeyDown = (e) => {
|
|
107
|
-
const parts = this.shortcut.split("+").map((p) => p.trim().toLowerCase());
|
|
108
|
-
const needsCtrl = parts.includes("ctrl");
|
|
109
|
-
const needsShift = parts.includes("shift");
|
|
110
|
-
const needsAlt = parts.includes("alt");
|
|
111
|
-
const key = parts.find((p) => !["ctrl", "shift", "alt"].includes(p));
|
|
112
|
-
if (!key) return;
|
|
113
|
-
const matches = e.ctrlKey === needsCtrl && e.shiftKey === needsShift && e.altKey === needsAlt && e.key.toLowerCase() === key;
|
|
114
|
-
if (matches) {
|
|
115
|
-
e.preventDefault();
|
|
116
|
-
this.toggle();
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
212
|
handleResize = () => {
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
this.state.pos.x = Math.max(0, Math.min(
|
|
123
|
-
this.state.pos.y = Math.max(0, Math.min(
|
|
213
|
+
const width = this.state.isOpen ? 400 : 140;
|
|
214
|
+
const height = this.state.isOpen ? 600 : 48;
|
|
215
|
+
this.state.pos.x = Math.max(0, Math.min(window.innerWidth - width, this.state.pos.x));
|
|
216
|
+
this.state.pos.y = Math.max(0, Math.min(window.innerHeight - height, this.state.pos.y));
|
|
124
217
|
this.render();
|
|
125
218
|
};
|
|
126
|
-
toggle() {
|
|
127
|
-
this.state.isOpen = !this.state.isOpen;
|
|
128
|
-
this.saveState();
|
|
129
|
-
this.render();
|
|
130
|
-
}
|
|
131
|
-
// --- DRAG LOGIC ---
|
|
132
|
-
totalDragDistance = 0;
|
|
133
219
|
handleMouseDown = (e) => {
|
|
134
|
-
const me = e;
|
|
135
|
-
const target = me.target;
|
|
136
|
-
const isHeader = target.closest(".header") || target.classList.contains("toggle-btn");
|
|
137
|
-
const isClose = target.closest("#close");
|
|
138
|
-
if (!isHeader || isClose) return;
|
|
139
|
-
this.isDragging = true;
|
|
140
220
|
this.totalDragDistance = 0;
|
|
221
|
+
const target = e.target;
|
|
222
|
+
const isHeader = target.closest(".header") || target.closest(".toggle-btn");
|
|
223
|
+
const isAction = target.closest("#close") || target.closest("[data-action]");
|
|
224
|
+
if (!isHeader || isAction) return;
|
|
225
|
+
this.isDragging = true;
|
|
141
226
|
this.offset = {
|
|
142
|
-
x:
|
|
143
|
-
y:
|
|
227
|
+
x: e.clientX - this.state.pos.x,
|
|
228
|
+
y: e.clientY - this.state.pos.y
|
|
144
229
|
};
|
|
145
230
|
const move = (ev) => {
|
|
146
231
|
if (!this.isDragging) return;
|
|
147
232
|
ev.preventDefault();
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
233
|
+
const dx = ev.clientX - (this.state.pos.x + this.offset.x);
|
|
234
|
+
const dy = ev.clientY - (this.state.pos.y + this.offset.y);
|
|
235
|
+
this.totalDragDistance += Math.abs(dx) + Math.abs(dy);
|
|
236
|
+
const width = this.state.isOpen ? 380 : 140;
|
|
237
|
+
const height = this.state.isOpen ? 400 : 48;
|
|
151
238
|
this.state.pos = {
|
|
152
239
|
x: Math.max(0, Math.min(window.innerWidth - width, ev.clientX - this.offset.x)),
|
|
153
240
|
y: Math.max(0, Math.min(window.innerHeight - height, ev.clientY - this.offset.y))
|
|
@@ -167,108 +254,35 @@ var PulseInspector = class extends HTMLElement {
|
|
|
167
254
|
document.addEventListener("mousemove", move);
|
|
168
255
|
document.addEventListener("mouseup", up);
|
|
169
256
|
};
|
|
170
|
-
// --- INTERACTION LOGIC ---
|
|
171
257
|
handleClick = (e) => {
|
|
172
|
-
if (this.totalDragDistance >
|
|
173
|
-
this.totalDragDistance = 0;
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
258
|
+
if (this.totalDragDistance > 10) return;
|
|
176
259
|
const target = e.target;
|
|
177
|
-
if (target.
|
|
178
|
-
this.
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
if (target.id === "refresh" || target.closest("#refresh")) {
|
|
182
|
-
this.refreshUnits();
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
const tabEl = target.closest(".tab-btn");
|
|
186
|
-
if (tabEl) {
|
|
187
|
-
const tab = tabEl.getAttribute("data-tab");
|
|
188
|
-
if (tab) {
|
|
189
|
-
this.state.activeTab = tab;
|
|
190
|
-
this.saveState();
|
|
191
|
-
this.render();
|
|
192
|
-
}
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
const valueEl = target.closest(".editable-value");
|
|
196
|
-
if (valueEl) {
|
|
197
|
-
const id = valueEl.getAttribute("data-id");
|
|
198
|
-
const name = valueEl.getAttribute("data-name");
|
|
199
|
-
const unit = this.units.find((u) => u._id === id || u._name === name);
|
|
200
|
-
if (unit && !("state" in unit)) {
|
|
201
|
-
this.editingUnit = unit;
|
|
202
|
-
this.editValue = JSON.stringify(unit());
|
|
203
|
-
this.render();
|
|
204
|
-
requestAnimationFrame(() => {
|
|
205
|
-
const input = this.shadow.querySelector(".edit-input");
|
|
206
|
-
if (input) {
|
|
207
|
-
input.focus();
|
|
208
|
-
input.select();
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
if (this.editingUnit && !target.closest(".edit-form")) {
|
|
215
|
-
this.editingUnit = null;
|
|
216
|
-
this.render();
|
|
217
|
-
}
|
|
218
|
-
const treeNode = target.closest(".tree-toggle");
|
|
219
|
-
if (treeNode) {
|
|
220
|
-
e.stopPropagation();
|
|
221
|
-
const id = treeNode.getAttribute("data-id");
|
|
222
|
-
if (id) {
|
|
223
|
-
if (this.state.expandedNodes.includes(id)) {
|
|
224
|
-
this.state.expandedNodes = this.state.expandedNodes.filter((n) => n !== id);
|
|
225
|
-
} else {
|
|
226
|
-
this.state.expandedNodes.push(id);
|
|
227
|
-
}
|
|
228
|
-
this.saveState();
|
|
229
|
-
this.render();
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
handleEditSubmit = (e) => {
|
|
234
|
-
e.preventDefault();
|
|
235
|
-
if (!this.editingUnit) return;
|
|
236
|
-
try {
|
|
237
|
-
const form = e.target;
|
|
238
|
-
const input = form.querySelector("input");
|
|
239
|
-
let val = input.value;
|
|
240
|
-
try {
|
|
241
|
-
if (val === "true") val = true;
|
|
242
|
-
else if (val === "false") val = false;
|
|
243
|
-
else if (val === "null") val = null;
|
|
244
|
-
else if (val === "undefined") val = void 0;
|
|
245
|
-
else if (!isNaN(Number(val)) && val.trim() !== "") val = Number(val);
|
|
246
|
-
else if (val.startsWith("{") || val.startsWith("[")) val = JSON.parse(val);
|
|
247
|
-
} catch (err) {
|
|
248
|
-
}
|
|
249
|
-
this.editingUnit.set(val);
|
|
250
|
-
this.editingUnit = null;
|
|
260
|
+
if (target.closest("#toggle") || target.closest("#close")) {
|
|
261
|
+
this.state.isOpen = !this.state.isOpen;
|
|
262
|
+
this.saveState();
|
|
251
263
|
this.render();
|
|
252
|
-
|
|
253
|
-
console.error("Pulse DevTools: Failed to update value", err);
|
|
264
|
+
return;
|
|
254
265
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
this.
|
|
266
|
+
const actionBtn = target.closest("[data-action]");
|
|
267
|
+
if (actionBtn) {
|
|
268
|
+
const type = actionBtn.getAttribute("data-action");
|
|
269
|
+
const uid = actionBtn.getAttribute("data-uid");
|
|
270
|
+
this.sendAction(type, uid);
|
|
260
271
|
}
|
|
261
272
|
};
|
|
262
|
-
|
|
273
|
+
sendAction(type, uid, payload) {
|
|
274
|
+
window.postMessage({
|
|
275
|
+
source: "pulse-ui",
|
|
276
|
+
action: { type, uid, payload }
|
|
277
|
+
}, "*");
|
|
278
|
+
}
|
|
263
279
|
render() {
|
|
264
280
|
if (!this.state.isOpen) {
|
|
265
281
|
this.shadow.innerHTML = this.getStyles() + `
|
|
266
282
|
<div class="container" style="left: ${this.state.pos.x}px; top: ${this.state.pos.y}px">
|
|
267
|
-
<div class="glass"
|
|
268
|
-
<div class="
|
|
269
|
-
|
|
270
|
-
Pulse (${this.units.length})
|
|
271
|
-
</div>
|
|
283
|
+
<div class="glass toggle-btn" id="toggle">
|
|
284
|
+
<div class="dot"></div>
|
|
285
|
+
Pulse (${this.units.length})
|
|
272
286
|
</div>
|
|
273
287
|
</div>
|
|
274
288
|
`;
|
|
@@ -276,177 +290,76 @@ var PulseInspector = class extends HTMLElement {
|
|
|
276
290
|
}
|
|
277
291
|
this.shadow.innerHTML = this.getStyles() + `
|
|
278
292
|
<div class="container" style="left: ${this.state.pos.x}px; top: ${this.state.pos.y}px">
|
|
279
|
-
<div class="glass">
|
|
293
|
+
<div class="glass window">
|
|
280
294
|
|
|
281
295
|
<div class="header">
|
|
282
296
|
<div style="display:flex;align-items:center;gap:10px">
|
|
283
297
|
<div class="dot" style="width:10px;height:10px"></div>
|
|
284
298
|
<strong style="font-size:14px;letter-spacing:0.5px">PULSE</strong>
|
|
285
|
-
<span style="font-size:10px;opacity:0.5;margin-top:2px">
|
|
299
|
+
<span style="font-size:10px;opacity:0.5;margin-top:2px">v2.0</span>
|
|
286
300
|
</div>
|
|
287
301
|
<div style="display:flex;gap:8px;align-items:center">
|
|
288
|
-
<div id="
|
|
289
|
-
<div id="close" class="icon-btn" title="Close (${this.shortcut})">\xD7</div>
|
|
290
|
-
</div>
|
|
291
|
-
</div>
|
|
292
|
-
|
|
293
|
-
<div class="tabs">
|
|
294
|
-
<div class="tab-btn ${this.state.activeTab === "inspector" ? "active" : ""}" data-tab="inspector">
|
|
295
|
-
${ICONS.list} Inspector
|
|
296
|
-
</div>
|
|
297
|
-
<div class="tab-btn ${this.state.activeTab === "tree" ? "active" : ""}" data-tab="tree">
|
|
298
|
-
${ICONS.tree} Pulse Tree
|
|
302
|
+
<div id="close" class="icon-btn" title="Minimize">\xD7</div>
|
|
299
303
|
</div>
|
|
300
304
|
</div>
|
|
301
305
|
|
|
302
306
|
<div class="content">
|
|
303
|
-
${this.
|
|
307
|
+
${this.units.length === 0 ? `<div class="empty-state">No reactive units detected.</div>` : `<div class="list">${this.units.map((u) => this.renderUnitCard(u)).join("")}</div>`}
|
|
304
308
|
</div>
|
|
305
309
|
|
|
306
310
|
</div>
|
|
307
311
|
</div>
|
|
308
312
|
`;
|
|
309
313
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
+
renderUnitCard(u) {
|
|
315
|
+
const status = u.status || "ok";
|
|
316
|
+
const hasDeps = u.dependencies && u.dependencies.length > 0;
|
|
314
317
|
return `
|
|
315
|
-
<div class="
|
|
316
|
-
${this.units.map((u) => this.renderUnitCard(u)).join("")}
|
|
317
|
-
</div>
|
|
318
|
-
`;
|
|
319
|
-
}
|
|
320
|
-
renderUnitCard(unit) {
|
|
321
|
-
const isGuard = "state" in unit;
|
|
322
|
-
const name = unit._name || "unnamed";
|
|
323
|
-
const id = unit._id;
|
|
324
|
-
const explanation = isGuard ? unit.explain() : null;
|
|
325
|
-
const value = isGuard ? explanation.value : unit();
|
|
326
|
-
let status = "ok";
|
|
327
|
-
if (isGuard) status = explanation.status;
|
|
328
|
-
const isEditing = this.editingUnit === unit;
|
|
329
|
-
return `
|
|
330
|
-
<div class="unit-card ${isGuard ? "" : "source-card"}" style="border-left-color: ${this.getStatusColor(status)}">
|
|
318
|
+
<div class="unit-card ${u.type}-card" style="border-left-color: ${this.getStatusColor(status)}">
|
|
331
319
|
<div class="unit-header">
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
320
|
+
<div style="display:flex;align-items:center;gap:6px">
|
|
321
|
+
<div class="status-dot status-${status}"></div>
|
|
322
|
+
<strong title="${u.name}">${u.name}</strong>
|
|
323
|
+
</div>
|
|
324
|
+
<span class="badg type-${u.type}">${u.type.toUpperCase()}</span>
|
|
337
325
|
</div>
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
<form class="edit-form">
|
|
342
|
-
<input class="edit-input" value='${this.safeStringify(value)}' />
|
|
343
|
-
</form>
|
|
344
|
-
` : `
|
|
345
|
-
<div class="value-row ${!isGuard ? "editable-value" : ""}"
|
|
346
|
-
data-id="${id}"
|
|
347
|
-
data-name="${name}"
|
|
348
|
-
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
|
-
` : ""}
|
|
326
|
+
<div class="value-row">
|
|
327
|
+
<span style="opacity:0.5;margin-right:6px">Value:</span>
|
|
328
|
+
<span class="value-text">${this.formatValue(u.value)}</span>
|
|
361
329
|
</div>
|
|
330
|
+
|
|
331
|
+
${u.reason ? `
|
|
332
|
+
<div class="reason-box">
|
|
333
|
+
<strong>${u.reason.code || "FAILURE"}</strong>
|
|
334
|
+
<div>${u.reason.message}</div>
|
|
335
|
+
</div>
|
|
336
|
+
` : ""}
|
|
337
|
+
|
|
338
|
+
${hasDeps ? `
|
|
339
|
+
<div class="deps-list">
|
|
340
|
+
<div class="deps-label">Dependencies:</div>
|
|
341
|
+
${u.dependencies.map((d) => `
|
|
342
|
+
<div class="dep-item">
|
|
343
|
+
<span class="dep-dot"></span>
|
|
344
|
+
${d.name}
|
|
345
|
+
</div>
|
|
346
|
+
`).join("")}
|
|
347
|
+
</div>
|
|
348
|
+
` : ""}
|
|
362
349
|
</div>
|
|
363
350
|
`;
|
|
364
351
|
}
|
|
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
352
|
formatValue(val) {
|
|
437
353
|
if (val === void 0) return "undefined";
|
|
438
354
|
if (val === null) return "null";
|
|
439
|
-
if (typeof val === "
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
if (val === void 0) return "undefined";
|
|
446
|
-
return JSON.stringify(val);
|
|
447
|
-
} catch {
|
|
448
|
-
return String(val);
|
|
355
|
+
if (typeof val === "object") {
|
|
356
|
+
try {
|
|
357
|
+
return JSON.stringify(val);
|
|
358
|
+
} catch (e) {
|
|
359
|
+
return "{...}";
|
|
360
|
+
}
|
|
449
361
|
}
|
|
362
|
+
return String(val);
|
|
450
363
|
}
|
|
451
364
|
getStatusColor(status) {
|
|
452
365
|
switch (status) {
|
|
@@ -463,163 +376,31 @@ var PulseInspector = class extends HTMLElement {
|
|
|
463
376
|
getStyles() {
|
|
464
377
|
return `
|
|
465
378
|
<style>
|
|
466
|
-
:host { all: initial; font-family: 'Inter', -
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
.
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
.glass {
|
|
478
|
-
background: ${COLORS.bg};
|
|
479
|
-
backdrop-filter: blur(12px);
|
|
480
|
-
border: 1px solid ${COLORS.border};
|
|
481
|
-
border-radius: 12px;
|
|
482
|
-
overflow: hidden;
|
|
483
|
-
display: flex;
|
|
484
|
-
flex-direction: column;
|
|
485
|
-
width: 400px;
|
|
486
|
-
max-height: 80vh;
|
|
487
|
-
transition: width 0.2s, height 0.2s;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/* TOGGLE BUTTON MODE */
|
|
491
|
-
.toggle-btn {
|
|
492
|
-
width: 140px;
|
|
493
|
-
height: 48px;
|
|
494
|
-
display: flex;
|
|
495
|
-
align-items: center;
|
|
496
|
-
justify-content: center;
|
|
497
|
-
gap: 10px;
|
|
498
|
-
cursor: pointer;
|
|
499
|
-
font-weight: 600;
|
|
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;
|
|
515
|
-
}
|
|
516
|
-
.dot {
|
|
517
|
-
width: 8px;
|
|
518
|
-
height: 8px;
|
|
519
|
-
border-radius: 50%;
|
|
520
|
-
background: ${COLORS.accent};
|
|
521
|
-
box-shadow: 0 0 12px ${COLORS.accentColor};
|
|
522
|
-
}
|
|
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);
|
|
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;
|
|
541
|
-
display: flex;
|
|
542
|
-
align-items: center;
|
|
543
|
-
justify-content: center;
|
|
544
|
-
gap: 6px;
|
|
545
|
-
}
|
|
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 {
|
|
551
|
-
flex: 1;
|
|
552
|
-
overflow-y: auto;
|
|
553
|
-
overflow-x: hidden;
|
|
554
|
-
min-height: 300px;
|
|
555
|
-
}
|
|
379
|
+
:host { all: initial; font-family: 'Inter', system-ui, sans-serif; }
|
|
380
|
+
.container { position: fixed; z-index: 1000000; filter: drop-shadow(0 8px 32px rgba(0,0,0,0.5)); color: white; }
|
|
381
|
+
.glass { background: ${COLORS.bg}; backdrop-filter: blur(12px); border: 1px solid ${COLORS.border}; border-radius: 12px; overflow: hidden; }
|
|
382
|
+
.window { width: 380px; max-height: 80vh; display: flex; flex-direction: column; }
|
|
383
|
+
.toggle-btn { width: 140px; height: 48px; display: flex; align-items: center; justify-content: center; gap: 10px; cursor: pointer; font-weight: 600; font-size: 13px; }
|
|
384
|
+
.header { padding: 12px 16px; background: rgba(0,0,0,0.2); border-bottom: 1px solid ${COLORS.border}; display: flex; justify-content: space-between; align-items: center; cursor: move; }
|
|
385
|
+
.dot { width: 8px; height: 8px; border-radius: 50%; background: ${COLORS.accent}; box-shadow: 0 0 12px ${COLORS.accentColor}; }
|
|
386
|
+
.icon-btn { cursor: pointer; opacity: 0.6; padding: 4px; font-size: 18px; line-height: 1; display: flex; align-items: center; }
|
|
387
|
+
.icon-btn:hover { opacity: 1; }
|
|
388
|
+
.content { flex: 1; overflow-y: auto; padding: 12px; min-height: 200px; }
|
|
556
389
|
.content::-webkit-scrollbar { width: 6px; }
|
|
557
390
|
.content::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 3px; }
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
.
|
|
561
|
-
.
|
|
562
|
-
|
|
563
|
-
.unit-card {
|
|
564
|
-
background: ${COLORS.cardBg};
|
|
565
|
-
border: 1px solid ${COLORS.border};
|
|
566
|
-
border-left: 3px solid transparent;
|
|
567
|
-
border-radius: 6px;
|
|
568
|
-
padding: 10px;
|
|
569
|
-
transition: background 0.1s;
|
|
570
|
-
}
|
|
571
|
-
.unit-card:hover { background: ${COLORS.cardHover}; }
|
|
572
|
-
.source-card { border-left-color: ${COLORS.secondaryText} !important; }
|
|
573
|
-
|
|
574
|
-
.unit-header {
|
|
575
|
-
display: flex;
|
|
576
|
-
justify-content: space-between;
|
|
577
|
-
align-items: center;
|
|
578
|
-
margin-bottom: 8px;
|
|
579
|
-
}
|
|
580
|
-
.status-dot {
|
|
581
|
-
width: 6px;
|
|
582
|
-
height: 6px;
|
|
583
|
-
border-radius: 50%;
|
|
584
|
-
}
|
|
391
|
+
.list { display: flex; flex-direction: column; gap: 8px; }
|
|
392
|
+
.unit-card { background: ${COLORS.cardBg}; border: 1px solid ${COLORS.border}; border-left: 3px solid transparent; border-radius: 6px; padding: 10px; }
|
|
393
|
+
.unit-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
|
|
394
|
+
.status-dot { width: 6px; height: 6px; border-radius: 50%; }
|
|
585
395
|
.status-ok { background: ${COLORS.success}; box-shadow: 0 0 6px ${COLORS.success}; }
|
|
586
396
|
.status-fail { background: ${COLORS.error}; box-shadow: 0 0 6px ${COLORS.error}; }
|
|
587
397
|
.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); }
|
|
398
|
+
.badg { font-size: 8px; font-weight: 700; padding: 2px 4px; border-radius: 3px; }
|
|
590
399
|
.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
|
-
}
|
|
400
|
+
.type-source { color: ${COLORS.secondaryText}; background: rgba(255,255,255,0.05); }
|
|
401
|
+
.value-row { font-size: 11px; font-family: monospace; background: rgba(0,0,0,0.2); padding: 6px 8px; border-radius: 4px; display: flex; align-items: center; justify-content: space-between; }
|
|
603
402
|
.value-text { color: ${COLORS.accentColor}; }
|
|
604
|
-
.
|
|
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;
|
|
615
|
-
font-family: monospace;
|
|
616
|
-
font-size: 11px;
|
|
617
|
-
padding: 6px;
|
|
618
|
-
border-radius: 4px;
|
|
619
|
-
outline: none;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
.reason {
|
|
403
|
+
.reason-box {
|
|
623
404
|
margin-top: 8px;
|
|
624
405
|
padding: 8px;
|
|
625
406
|
background: rgba(255, 75, 43, 0.1);
|
|
@@ -628,30 +409,14 @@ var PulseInspector = class extends HTMLElement {
|
|
|
628
409
|
color: ${COLORS.error};
|
|
629
410
|
font-size: 11px;
|
|
630
411
|
}
|
|
631
|
-
.
|
|
632
|
-
.
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
.
|
|
636
|
-
.
|
|
637
|
-
.
|
|
638
|
-
|
|
639
|
-
align-items: center;
|
|
640
|
-
padding: 4px 6px;
|
|
641
|
-
border-radius: 4px;
|
|
642
|
-
cursor: pointer;
|
|
643
|
-
user-select: none;
|
|
644
|
-
}
|
|
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;
|
|
651
|
-
}
|
|
652
|
-
.mini-badge.fail { background: ${COLORS.error}; color: white; opacity: 1; }
|
|
653
|
-
.mini-badge.source { background: rgba(255,255,255,0.1); }
|
|
654
|
-
|
|
412
|
+
.reason-box strong { font-size: 10px; display: block; opacity: 0.8; margin-bottom: 2px; }
|
|
413
|
+
.deps-list { margin-top: 10px; border-top: 1px solid ${COLORS.border}; padding-top: 8px; }
|
|
414
|
+
.deps-label { font-size: 9px; opacity: 0.5; font-weight: bold; margin-bottom: 4px; text-transform: uppercase; }
|
|
415
|
+
.dep-item { font-size: 10px; opacity: 0.8; display: flex; align-items: center; gap: 6px; padding: 2px 0; }
|
|
416
|
+
.dep-dot { width: 4px; height: 4px; background: ${COLORS.accentColor}; border-radius: 50%; opacity: 0.5; }
|
|
417
|
+
.error-text { color: ${COLORS.error}; font-size: 10px; margin-top: 6px; }
|
|
418
|
+
.empty-state { padding: 40px; text-align: center; opacity: 0.4; font-size: 12px; }
|
|
419
|
+
.mini-btn { background: ${COLORS.accent}; border: none; color: white; border-radius: 3px; font-size: 9px; padding: 2px 6px; cursor: pointer; }
|
|
655
420
|
</style>
|
|
656
421
|
`;
|
|
657
422
|
}
|
|
@@ -659,6 +424,53 @@ var PulseInspector = class extends HTMLElement {
|
|
|
659
424
|
if (!customElements.get("pulse-inspector")) {
|
|
660
425
|
customElements.define("pulse-inspector", PulseInspector);
|
|
661
426
|
}
|
|
427
|
+
|
|
428
|
+
// src/timetravel.ts
|
|
429
|
+
import { PulseRegistry as PulseRegistry2 } from "@pulse-js/core";
|
|
430
|
+
var TimeTravel = class {
|
|
431
|
+
snapshots = [];
|
|
432
|
+
maxSnapshots = 100;
|
|
433
|
+
/**
|
|
434
|
+
* Captures a point-in-time snapshot of all registered sources.
|
|
435
|
+
*/
|
|
436
|
+
capture() {
|
|
437
|
+
const data = {};
|
|
438
|
+
const units = PulseRegistry2.getAllWithMeta();
|
|
439
|
+
units.forEach(({ unit, uid }) => {
|
|
440
|
+
if (!unit.state) {
|
|
441
|
+
data[uid] = unit();
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
this.snapshots.push({
|
|
445
|
+
timestamp: Date.now(),
|
|
446
|
+
data
|
|
447
|
+
});
|
|
448
|
+
if (this.snapshots.length > this.maxSnapshots) {
|
|
449
|
+
this.snapshots.shift();
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Reverts the registry state to a specific snapshot index.
|
|
454
|
+
*/
|
|
455
|
+
travel(index) {
|
|
456
|
+
const snapshot = this.snapshots[index];
|
|
457
|
+
if (!snapshot) return;
|
|
458
|
+
PulseRegistry2.getAllWithMeta().forEach(({ unit, uid }) => {
|
|
459
|
+
const value = snapshot.data[uid];
|
|
460
|
+
if (value !== void 0 && unit.set) {
|
|
461
|
+
unit.set(value);
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
getHistory() {
|
|
466
|
+
return this.snapshots;
|
|
467
|
+
}
|
|
468
|
+
clear() {
|
|
469
|
+
this.snapshots = [];
|
|
470
|
+
}
|
|
471
|
+
};
|
|
662
472
|
export {
|
|
663
|
-
|
|
473
|
+
PulseAgent,
|
|
474
|
+
PulseInspector,
|
|
475
|
+
TimeTravel
|
|
664
476
|
};
|