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