@omniaura/solid-pulse 0.1.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/README.md +128 -0
- package/dist/bridge.d.ts +202 -0
- package/dist/bridge.js +10 -0
- package/dist/bridge.js.map +1 -0
- package/dist/chunk-4QA2G6S3.js +15 -0
- package/dist/chunk-4QA2G6S3.js.map +1 -0
- package/dist/chunk-5FYH2KEZ.js +66 -0
- package/dist/chunk-5FYH2KEZ.js.map +1 -0
- package/dist/chunk-C72EYM65.js +462 -0
- package/dist/chunk-C72EYM65.js.map +1 -0
- package/dist/chunk-IXNWEUNF.js +114 -0
- package/dist/chunk-IXNWEUNF.js.map +1 -0
- package/dist/chunk-TVSI7G5S.js +414 -0
- package/dist/chunk-TVSI7G5S.js.map +1 -0
- package/dist/chunk-WIMCBTHZ.js +21 -0
- package/dist/chunk-WIMCBTHZ.js.map +1 -0
- package/dist/cli.js +229 -0
- package/dist/cli.js.map +1 -0
- package/dist/controller-3akN6Qi0.d.ts +210 -0
- package/dist/core.d.ts +76 -0
- package/dist/core.js +41 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +175 -0
- package/dist/index.js +987 -0
- package/dist/index.js.map +1 -0
- package/dist/panel.d.ts +34 -0
- package/dist/panel.js +408 -0
- package/dist/panel.js.map +1 -0
- package/dist/query.d.ts +93 -0
- package/dist/query.js +164 -0
- package/dist/query.js.map +1 -0
- package/dist/vite.d.ts +41 -0
- package/dist/vite.js +76 -0
- package/dist/vite.js.map +1 -0
- package/package.json +99 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
// src/core/ring-buffer.ts
|
|
2
|
+
var RingBuffer = class {
|
|
3
|
+
constructor(capacity) {
|
|
4
|
+
this.capacity = capacity;
|
|
5
|
+
if (!Number.isInteger(capacity) || capacity <= 0) {
|
|
6
|
+
throw new RangeError(`RingBuffer capacity must be a positive integer, got ${capacity}`);
|
|
7
|
+
}
|
|
8
|
+
this.items = new Array(capacity);
|
|
9
|
+
}
|
|
10
|
+
capacity;
|
|
11
|
+
items;
|
|
12
|
+
head = 0;
|
|
13
|
+
count = 0;
|
|
14
|
+
/** Total number of pushes since creation (dropped + retained). */
|
|
15
|
+
pushed = 0;
|
|
16
|
+
get size() {
|
|
17
|
+
return this.count;
|
|
18
|
+
}
|
|
19
|
+
get dropped() {
|
|
20
|
+
return this.pushed - this.count;
|
|
21
|
+
}
|
|
22
|
+
push(item) {
|
|
23
|
+
const idx = (this.head + this.count) % this.capacity;
|
|
24
|
+
if (this.count === this.capacity) {
|
|
25
|
+
this.items[this.head] = item;
|
|
26
|
+
this.head = (this.head + 1) % this.capacity;
|
|
27
|
+
} else {
|
|
28
|
+
this.items[idx] = item;
|
|
29
|
+
this.count++;
|
|
30
|
+
}
|
|
31
|
+
this.pushed++;
|
|
32
|
+
}
|
|
33
|
+
/** Oldest → newest. */
|
|
34
|
+
toArray() {
|
|
35
|
+
const out = new Array(this.count);
|
|
36
|
+
for (let i = 0; i < this.count; i++) {
|
|
37
|
+
out[i] = this.items[(this.head + i) % this.capacity];
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
clear() {
|
|
42
|
+
this.items = new Array(this.capacity);
|
|
43
|
+
this.head = 0;
|
|
44
|
+
this.count = 0;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// src/core/bus.ts
|
|
49
|
+
var EventBus = class {
|
|
50
|
+
buffer;
|
|
51
|
+
listeners = /* @__PURE__ */ new Set();
|
|
52
|
+
seq = 0;
|
|
53
|
+
recording = null;
|
|
54
|
+
recordings = /* @__PURE__ */ new Map();
|
|
55
|
+
/** Kinds that are muted at the source (never buffered). */
|
|
56
|
+
muted = /* @__PURE__ */ new Set();
|
|
57
|
+
paused = false;
|
|
58
|
+
constructor(capacity = 2e3) {
|
|
59
|
+
this.buffer = new RingBuffer(capacity);
|
|
60
|
+
}
|
|
61
|
+
get nextSeq() {
|
|
62
|
+
return this.seq + 1;
|
|
63
|
+
}
|
|
64
|
+
emit(kind, data, extra = {}) {
|
|
65
|
+
if (this.paused || this.muted.has(kind)) return null;
|
|
66
|
+
const event = {
|
|
67
|
+
seq: ++this.seq,
|
|
68
|
+
t: typeof performance !== "undefined" ? performance.now() : Date.now(),
|
|
69
|
+
wall: Date.now(),
|
|
70
|
+
kind,
|
|
71
|
+
data,
|
|
72
|
+
...extra
|
|
73
|
+
};
|
|
74
|
+
this.buffer.push(event);
|
|
75
|
+
const rec = this.recording;
|
|
76
|
+
if (rec) {
|
|
77
|
+
rec.events.push(event);
|
|
78
|
+
if (rec.events.length >= rec.limit) this.stopRecording();
|
|
79
|
+
}
|
|
80
|
+
for (const l of this.listeners) {
|
|
81
|
+
try {
|
|
82
|
+
l(event);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
if (typeof console !== "undefined") console.warn("[solid-pulse] listener failed", err);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return event;
|
|
88
|
+
}
|
|
89
|
+
subscribe(listener) {
|
|
90
|
+
this.listeners.add(listener);
|
|
91
|
+
return () => this.listeners.delete(listener);
|
|
92
|
+
}
|
|
93
|
+
/** Events with seq > since (oldest first), optionally filtered by kind. */
|
|
94
|
+
list(opts = {}) {
|
|
95
|
+
const { since = 0, kinds = null, limit = 500 } = opts;
|
|
96
|
+
const all = this.buffer.toArray();
|
|
97
|
+
const out = [];
|
|
98
|
+
for (const e of all) {
|
|
99
|
+
if (e.seq <= since) continue;
|
|
100
|
+
if (kinds && kinds.size > 0 && !kinds.has(e.kind)) continue;
|
|
101
|
+
out.push(e);
|
|
102
|
+
}
|
|
103
|
+
return out.length > limit ? out.slice(out.length - limit) : out;
|
|
104
|
+
}
|
|
105
|
+
clear() {
|
|
106
|
+
this.buffer.clear();
|
|
107
|
+
}
|
|
108
|
+
startRecording(id, limit = 2e4) {
|
|
109
|
+
if (this.recording) this.stopRecording();
|
|
110
|
+
const rec = {
|
|
111
|
+
id: id ?? `rec-${Date.now().toString(36)}-${this.seq.toString(36)}`,
|
|
112
|
+
startedAt: typeof performance !== "undefined" ? performance.now() : Date.now(),
|
|
113
|
+
startedWall: Date.now(),
|
|
114
|
+
stoppedAt: null,
|
|
115
|
+
events: [],
|
|
116
|
+
limit
|
|
117
|
+
};
|
|
118
|
+
this.recording = rec;
|
|
119
|
+
this.recordings.set(rec.id, rec);
|
|
120
|
+
while (this.recordings.size > 5) {
|
|
121
|
+
const oldest = this.recordings.keys().next().value;
|
|
122
|
+
if (oldest === void 0) break;
|
|
123
|
+
this.recordings.delete(oldest);
|
|
124
|
+
}
|
|
125
|
+
return rec;
|
|
126
|
+
}
|
|
127
|
+
stopRecording() {
|
|
128
|
+
const rec = this.recording;
|
|
129
|
+
if (!rec) return null;
|
|
130
|
+
rec.stoppedAt = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
131
|
+
this.recording = null;
|
|
132
|
+
return rec;
|
|
133
|
+
}
|
|
134
|
+
currentRecording() {
|
|
135
|
+
return this.recording;
|
|
136
|
+
}
|
|
137
|
+
getRecording(id) {
|
|
138
|
+
return this.recordings.get(id) ?? null;
|
|
139
|
+
}
|
|
140
|
+
listRecordings() {
|
|
141
|
+
return [...this.recordings.values()].map((r) => ({
|
|
142
|
+
id: r.id,
|
|
143
|
+
startedWall: r.startedWall,
|
|
144
|
+
stoppedAt: r.stoppedAt,
|
|
145
|
+
events: r.events.length,
|
|
146
|
+
active: r === this.recording
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// src/core/events.ts
|
|
152
|
+
var KIND_GROUPS = {
|
|
153
|
+
solid: [
|
|
154
|
+
"solid.flush",
|
|
155
|
+
"solid.computation",
|
|
156
|
+
"solid.component.mount",
|
|
157
|
+
"solid.component.dispose",
|
|
158
|
+
"solid.component.remount",
|
|
159
|
+
"solid.root"
|
|
160
|
+
],
|
|
161
|
+
dom: ["dom.mutation", "dom.detach", "dom.reattach", "focus.lost"],
|
|
162
|
+
net: [
|
|
163
|
+
"net.fetch.start",
|
|
164
|
+
"net.fetch.end",
|
|
165
|
+
"net.fetch.error",
|
|
166
|
+
"net.ws.open",
|
|
167
|
+
"net.ws.message",
|
|
168
|
+
"net.ws.close",
|
|
169
|
+
"net.ws.error",
|
|
170
|
+
"net.sse.open",
|
|
171
|
+
"net.sse.message",
|
|
172
|
+
"net.sse.error",
|
|
173
|
+
"net.sse.close"
|
|
174
|
+
],
|
|
175
|
+
query: [
|
|
176
|
+
"query.added",
|
|
177
|
+
"query.removed",
|
|
178
|
+
"query.observe",
|
|
179
|
+
"query.unobserve",
|
|
180
|
+
"query.fetch.start",
|
|
181
|
+
"query.fetch.success",
|
|
182
|
+
"query.fetch.error",
|
|
183
|
+
"query.invalidate",
|
|
184
|
+
"query.update",
|
|
185
|
+
"mutation.start",
|
|
186
|
+
"mutation.success",
|
|
187
|
+
"mutation.error"
|
|
188
|
+
],
|
|
189
|
+
pulse: ["pulse.note"]
|
|
190
|
+
};
|
|
191
|
+
var ALL_KINDS = Object.values(KIND_GROUPS).flat();
|
|
192
|
+
function expandKinds(filters) {
|
|
193
|
+
const out = /* @__PURE__ */ new Set();
|
|
194
|
+
for (const raw of filters) {
|
|
195
|
+
const f = raw.trim();
|
|
196
|
+
if (!f) continue;
|
|
197
|
+
if (f === "*" || f === "all") {
|
|
198
|
+
for (const k of ALL_KINDS) out.add(k);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const group = KIND_GROUPS[f];
|
|
202
|
+
if (group) {
|
|
203
|
+
for (const k of group) out.add(k);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (f.endsWith("*")) {
|
|
207
|
+
const prefix = f.slice(0, -1);
|
|
208
|
+
for (const k of ALL_KINDS) if (k.startsWith(prefix)) out.add(k);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (ALL_KINDS.includes(f)) out.add(f);
|
|
212
|
+
}
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/core/controller.ts
|
|
217
|
+
var FEATURES = [
|
|
218
|
+
"solid",
|
|
219
|
+
"dom",
|
|
220
|
+
"flash",
|
|
221
|
+
"network",
|
|
222
|
+
"query",
|
|
223
|
+
"queryOverlay",
|
|
224
|
+
"verboseComputations",
|
|
225
|
+
"captureBodies"
|
|
226
|
+
];
|
|
227
|
+
var PulseController = class {
|
|
228
|
+
bus;
|
|
229
|
+
commands = /* @__PURE__ */ new Map();
|
|
230
|
+
features;
|
|
231
|
+
featureListeners = /* @__PURE__ */ new Set();
|
|
232
|
+
filters = { kinds: [], component: "", text: "" };
|
|
233
|
+
filterListeners = /* @__PURE__ */ new Set();
|
|
234
|
+
startedWall = Date.now();
|
|
235
|
+
constructor(bus = new EventBus(), initial = {}) {
|
|
236
|
+
this.bus = bus;
|
|
237
|
+
this.features = {
|
|
238
|
+
solid: true,
|
|
239
|
+
dom: true,
|
|
240
|
+
flash: true,
|
|
241
|
+
network: true,
|
|
242
|
+
query: true,
|
|
243
|
+
queryOverlay: true,
|
|
244
|
+
verboseComputations: false,
|
|
245
|
+
captureBodies: false,
|
|
246
|
+
...initial
|
|
247
|
+
};
|
|
248
|
+
this.registerCore();
|
|
249
|
+
}
|
|
250
|
+
// ── features ─────────────────────────────────────────────────────
|
|
251
|
+
isOn(feature) {
|
|
252
|
+
return this.features[feature];
|
|
253
|
+
}
|
|
254
|
+
setFeature(feature, on) {
|
|
255
|
+
if (!FEATURES.includes(feature)) throw new Error(`unknown feature: ${feature}`);
|
|
256
|
+
if (this.features[feature] === on) return;
|
|
257
|
+
this.features[feature] = on;
|
|
258
|
+
for (const l of this.featureListeners) l(feature, on);
|
|
259
|
+
this.bus.emit("pulse.note", { note: `feature ${feature} ${on ? "on" : "off"}` });
|
|
260
|
+
}
|
|
261
|
+
onFeature(listener) {
|
|
262
|
+
this.featureListeners.add(listener);
|
|
263
|
+
return () => this.featureListeners.delete(listener);
|
|
264
|
+
}
|
|
265
|
+
snapshotFeatures() {
|
|
266
|
+
return { ...this.features };
|
|
267
|
+
}
|
|
268
|
+
// ── filters ──────────────────────────────────────────────────────
|
|
269
|
+
setFilters(next) {
|
|
270
|
+
this.filters = { ...this.filters, ...next };
|
|
271
|
+
for (const l of this.filterListeners) l(this.filters);
|
|
272
|
+
}
|
|
273
|
+
onFilters(listener) {
|
|
274
|
+
this.filterListeners.add(listener);
|
|
275
|
+
return () => this.filterListeners.delete(listener);
|
|
276
|
+
}
|
|
277
|
+
matchesFilters(e, f = this.filters) {
|
|
278
|
+
if (f.kinds.length) {
|
|
279
|
+
const kinds = expandKinds(f.kinds);
|
|
280
|
+
if (kinds.size && !kinds.has(e.kind)) return false;
|
|
281
|
+
}
|
|
282
|
+
if (f.component) {
|
|
283
|
+
const name = e.component?.name ?? "";
|
|
284
|
+
const chain = e.component?.chain?.join(" ") ?? "";
|
|
285
|
+
const needle = f.component.toLowerCase();
|
|
286
|
+
if (!name.toLowerCase().includes(needle) && !chain.toLowerCase().includes(needle)) return false;
|
|
287
|
+
}
|
|
288
|
+
if (f.text) {
|
|
289
|
+
const needle = f.text.toLowerCase();
|
|
290
|
+
if (!JSON.stringify(e.data).toLowerCase().includes(needle)) return false;
|
|
291
|
+
}
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
/** Buffered events after `since`, through the active filters. */
|
|
295
|
+
events(opts = {}) {
|
|
296
|
+
const kinds = opts.kinds ? expandKinds(opts.kinds) : null;
|
|
297
|
+
const list = this.bus.list({ since: opts.since, limit: opts.raw ? opts.limit : 5e3, kinds });
|
|
298
|
+
const filtered = opts.raw ? list : list.filter((e) => this.matchesFilters(e));
|
|
299
|
+
const limit = opts.limit ?? 500;
|
|
300
|
+
return filtered.length > limit ? filtered.slice(filtered.length - limit) : filtered;
|
|
301
|
+
}
|
|
302
|
+
// ── commands ─────────────────────────────────────────────────────
|
|
303
|
+
register(spec, handler) {
|
|
304
|
+
if (this.commands.has(spec.name)) throw new Error(`command already registered: ${spec.name}`);
|
|
305
|
+
this.commands.set(spec.name, { spec, handler });
|
|
306
|
+
}
|
|
307
|
+
unregister(name) {
|
|
308
|
+
this.commands.delete(name);
|
|
309
|
+
}
|
|
310
|
+
has(name) {
|
|
311
|
+
return this.commands.has(name);
|
|
312
|
+
}
|
|
313
|
+
describe() {
|
|
314
|
+
return [...this.commands.values()].map((c) => c.spec).sort((a, b) => a.name.localeCompare(b.name));
|
|
315
|
+
}
|
|
316
|
+
async run(name, args = {}) {
|
|
317
|
+
const cmd = this.commands.get(name);
|
|
318
|
+
if (!cmd) return { ok: false, error: `unknown command: ${name}` };
|
|
319
|
+
try {
|
|
320
|
+
const value = await cmd.handler(args ?? {});
|
|
321
|
+
return { ok: true, value: value === void 0 ? null : value };
|
|
322
|
+
} catch (err) {
|
|
323
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
registerCore() {
|
|
327
|
+
this.register(
|
|
328
|
+
{ name: "status", summary: "Runtime status: features, filters, buffer stats, recording.", ui: "panel header" },
|
|
329
|
+
() => ({
|
|
330
|
+
startedWall: this.startedWall,
|
|
331
|
+
features: this.snapshotFeatures(),
|
|
332
|
+
filters: this.filters,
|
|
333
|
+
buffer: { size: this.bus.buffer.size, capacity: this.bus.buffer.capacity, dropped: this.bus.buffer.dropped, seq: this.bus.nextSeq - 1 },
|
|
334
|
+
paused: this.bus.paused,
|
|
335
|
+
recording: this.bus.currentRecording()?.id ?? null,
|
|
336
|
+
commands: this.commands.size
|
|
337
|
+
})
|
|
338
|
+
);
|
|
339
|
+
this.register(
|
|
340
|
+
{ name: "commands", summary: "List every command with its panel equivalent (the parity contract)." },
|
|
341
|
+
() => this.describe()
|
|
342
|
+
);
|
|
343
|
+
this.register(
|
|
344
|
+
{ name: "features.list", summary: "Feature flags and their current state.", ui: "Pulse tab \u203A toggles" },
|
|
345
|
+
() => this.snapshotFeatures()
|
|
346
|
+
);
|
|
347
|
+
this.register(
|
|
348
|
+
{
|
|
349
|
+
name: "features.set",
|
|
350
|
+
summary: "Turn a feature on/off.",
|
|
351
|
+
args: { name: FEATURES.join("|"), on: "true|false (default true)" },
|
|
352
|
+
ui: "Pulse tab \u203A toggles"
|
|
353
|
+
},
|
|
354
|
+
(a) => {
|
|
355
|
+
const name = String(a.name ?? "");
|
|
356
|
+
const on = a.on === void 0 ? true : a.on === true || a.on === "true" || a.on === "on" || a.on === 1;
|
|
357
|
+
this.setFeature(name, on);
|
|
358
|
+
return this.snapshotFeatures();
|
|
359
|
+
}
|
|
360
|
+
);
|
|
361
|
+
this.register(
|
|
362
|
+
{
|
|
363
|
+
name: "filters.set",
|
|
364
|
+
summary: "Set event filters (kinds list, component substring, text substring). Empty string clears.",
|
|
365
|
+
args: { kinds: "comma list: solid,dom,net,query or kinds/globs", component: "substring", text: "substring" },
|
|
366
|
+
ui: "Pulse tab \u203A filter bar"
|
|
367
|
+
},
|
|
368
|
+
(a) => {
|
|
369
|
+
const next = {};
|
|
370
|
+
if (a.kinds !== void 0) {
|
|
371
|
+
next.kinds = Array.isArray(a.kinds) ? a.kinds.map(String) : String(a.kinds).split(",").map((s) => s.trim()).filter(Boolean);
|
|
372
|
+
}
|
|
373
|
+
if (a.component !== void 0) next.component = String(a.component);
|
|
374
|
+
if (a.text !== void 0) next.text = String(a.text);
|
|
375
|
+
this.setFilters(next);
|
|
376
|
+
return this.filters;
|
|
377
|
+
}
|
|
378
|
+
);
|
|
379
|
+
this.register({ name: "filters.get", summary: "Current filters.", ui: "Pulse tab \u203A filter bar" }, () => this.filters);
|
|
380
|
+
this.register(
|
|
381
|
+
{
|
|
382
|
+
name: "events.list",
|
|
383
|
+
summary: "Buffered events (oldest first) through the active filters.",
|
|
384
|
+
args: { since: "seq to start after", limit: "max rows (default 200)", kinds: "override kind filter", raw: "true = ignore filters" },
|
|
385
|
+
ui: "Pulse tab \u203A event list"
|
|
386
|
+
},
|
|
387
|
+
(a) => {
|
|
388
|
+
const kinds = a.kinds === void 0 ? void 0 : String(a.kinds).split(",").map((s) => s.trim()).filter(Boolean);
|
|
389
|
+
return this.events({
|
|
390
|
+
since: a.since === void 0 ? void 0 : Number(a.since),
|
|
391
|
+
limit: a.limit === void 0 ? 200 : Number(a.limit),
|
|
392
|
+
kinds,
|
|
393
|
+
raw: a.raw === true || a.raw === "true"
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
);
|
|
397
|
+
this.register({ name: "events.clear", summary: "Clear the event buffer.", ui: "Pulse tab \u203A Clear" }, () => {
|
|
398
|
+
this.bus.clear();
|
|
399
|
+
return { cleared: true };
|
|
400
|
+
});
|
|
401
|
+
this.register({ name: "events.pause", summary: "Stop capturing new events (buffer kept).", ui: "Pulse tab \u203A Pause" }, () => {
|
|
402
|
+
this.bus.paused = true;
|
|
403
|
+
return { paused: true };
|
|
404
|
+
});
|
|
405
|
+
this.register({ name: "events.resume", summary: "Resume capturing.", ui: "Pulse tab \u203A Pause" }, () => {
|
|
406
|
+
this.bus.paused = false;
|
|
407
|
+
return { paused: false };
|
|
408
|
+
});
|
|
409
|
+
this.register(
|
|
410
|
+
{ name: "note", summary: "Append a marker note to the timeline (agents: label a QA step).", args: { text: "note text" }, ui: "Record tab \u203A Note" },
|
|
411
|
+
(a) => this.bus.emit("pulse.note", { note: String(a.text ?? "") })
|
|
412
|
+
);
|
|
413
|
+
this.register(
|
|
414
|
+
{ name: "record.start", summary: "Start a recording (stops any active one).", args: { id: "optional id", limit: "max events (default 20000)" }, ui: "Record tab \u203A Start" },
|
|
415
|
+
(a) => {
|
|
416
|
+
const rec = this.bus.startRecording(a.id === void 0 ? void 0 : String(a.id), a.limit === void 0 ? void 0 : Number(a.limit));
|
|
417
|
+
return { id: rec.id, startedWall: rec.startedWall };
|
|
418
|
+
}
|
|
419
|
+
);
|
|
420
|
+
this.register({ name: "record.stop", summary: "Stop the active recording.", ui: "Record tab \u203A Stop" }, () => {
|
|
421
|
+
const rec = this.bus.stopRecording();
|
|
422
|
+
return rec ? { id: rec.id, events: rec.events.length } : null;
|
|
423
|
+
});
|
|
424
|
+
this.register({ name: "record.list", summary: "Recordings kept in memory (last 5).", ui: "Record tab \u203A list" }, () => this.bus.listRecordings());
|
|
425
|
+
this.register(
|
|
426
|
+
{
|
|
427
|
+
name: "export",
|
|
428
|
+
summary: "Export a recording (or the live buffer) as JSON: {meta, events}.",
|
|
429
|
+
args: { recording: "recording id; omit for the live buffer", filtered: "true = apply filters to the live buffer" },
|
|
430
|
+
ui: "Record tab \u203A Export"
|
|
431
|
+
},
|
|
432
|
+
(a) => {
|
|
433
|
+
const id = a.recording === void 0 ? null : String(a.recording);
|
|
434
|
+
const rec = id ? this.bus.getRecording(id) : null;
|
|
435
|
+
if (id && !rec) throw new Error(`unknown recording: ${id}`);
|
|
436
|
+
const events = rec ? rec.events : a.filtered === true || a.filtered === "true" ? this.events({ limit: 1e5 }) : this.bus.list({ limit: 1e5 });
|
|
437
|
+
return {
|
|
438
|
+
meta: {
|
|
439
|
+
tool: "@omniaura/solid-pulse",
|
|
440
|
+
exportedWall: Date.now(),
|
|
441
|
+
recording: rec ? { id: rec.id, startedWall: rec.startedWall, stoppedAt: rec.stoppedAt } : null,
|
|
442
|
+
features: this.snapshotFeatures(),
|
|
443
|
+
filters: this.filters,
|
|
444
|
+
count: events.length
|
|
445
|
+
},
|
|
446
|
+
events
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
export {
|
|
454
|
+
RingBuffer,
|
|
455
|
+
EventBus,
|
|
456
|
+
KIND_GROUPS,
|
|
457
|
+
ALL_KINDS,
|
|
458
|
+
expandKinds,
|
|
459
|
+
FEATURES,
|
|
460
|
+
PulseController
|
|
461
|
+
};
|
|
462
|
+
//# sourceMappingURL=chunk-C72EYM65.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/ring-buffer.ts","../src/core/bus.ts","../src/core/events.ts","../src/core/controller.ts"],"sourcesContent":["/** Fixed-capacity FIFO. Overwrites the oldest entry; never grows. */\nexport class RingBuffer<T> {\n private items: (T | undefined)[];\n private head = 0;\n private count = 0;\n /** Total number of pushes since creation (dropped + retained). */\n pushed = 0;\n\n constructor(public readonly capacity: number) {\n if (!Number.isInteger(capacity) || capacity <= 0) {\n throw new RangeError(`RingBuffer capacity must be a positive integer, got ${capacity}`);\n }\n this.items = new Array(capacity);\n }\n\n get size() {\n return this.count;\n }\n\n get dropped() {\n return this.pushed - this.count;\n }\n\n push(item: T) {\n const idx = (this.head + this.count) % this.capacity;\n if (this.count === this.capacity) {\n this.items[this.head] = item;\n this.head = (this.head + 1) % this.capacity;\n } else {\n this.items[idx] = item;\n this.count++;\n }\n this.pushed++;\n }\n\n /** Oldest → newest. */\n toArray(): T[] {\n const out: T[] = new Array(this.count);\n for (let i = 0; i < this.count; i++) {\n out[i] = this.items[(this.head + i) % this.capacity] as T;\n }\n return out;\n }\n\n clear() {\n this.items = new Array(this.capacity);\n this.head = 0;\n this.count = 0;\n }\n}\n","import { RingBuffer } from \"./ring-buffer.js\";\nimport type { PulseEvent, PulseEventKind } from \"./events.js\";\n\nexport type Listener = (event: PulseEvent) => void;\n\nexport interface Recording {\n id: string;\n startedAt: number;\n startedWall: number;\n stoppedAt: number | null;\n events: PulseEvent[];\n /** Hard cap; the recording stops itself when reached. */\n limit: number;\n}\n\n/**\n * Bounded event bus. Emission is synchronous and cheap: one ring-buffer push,\n * one optional recording push, then listeners. Filters are applied by\n * consumers (panel/bridge), not here, so the buffer stays a faithful log.\n */\nexport class EventBus {\n readonly buffer: RingBuffer<PulseEvent>;\n private listeners = new Set<Listener>();\n private seq = 0;\n private recording: Recording | null = null;\n private recordings = new Map<string, Recording>();\n /** Kinds that are muted at the source (never buffered). */\n muted = new Set<PulseEventKind>();\n paused = false;\n\n constructor(capacity = 2000) {\n this.buffer = new RingBuffer<PulseEvent>(capacity);\n }\n\n get nextSeq() {\n return this.seq + 1;\n }\n\n emit(kind: PulseEventKind, data: Record<string, unknown>, extra: Partial<PulseEvent> = {}): PulseEvent | null {\n if (this.paused || this.muted.has(kind)) return null;\n const event: PulseEvent = {\n seq: ++this.seq,\n t: typeof performance !== \"undefined\" ? performance.now() : Date.now(),\n wall: Date.now(),\n kind,\n data,\n ...extra,\n };\n this.buffer.push(event);\n const rec = this.recording;\n if (rec) {\n rec.events.push(event);\n if (rec.events.length >= rec.limit) this.stopRecording();\n }\n for (const l of this.listeners) {\n try {\n l(event);\n } catch (err) {\n // A misbehaving listener must never break the app being observed.\n if (typeof console !== \"undefined\") console.warn(\"[solid-pulse] listener failed\", err);\n }\n }\n return event;\n }\n\n subscribe(listener: Listener): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /** Events with seq > since (oldest first), optionally filtered by kind. */\n list(opts: { since?: number; kinds?: Set<PulseEventKind> | null; limit?: number } = {}): PulseEvent[] {\n const { since = 0, kinds = null, limit = 500 } = opts;\n const all = this.buffer.toArray();\n const out: PulseEvent[] = [];\n for (const e of all) {\n if (e.seq <= since) continue;\n if (kinds && kinds.size > 0 && !kinds.has(e.kind)) continue;\n out.push(e);\n }\n return out.length > limit ? out.slice(out.length - limit) : out;\n }\n\n clear() {\n this.buffer.clear();\n }\n\n startRecording(id?: string, limit = 20_000): Recording {\n if (this.recording) this.stopRecording();\n const rec: Recording = {\n id: id ?? `rec-${Date.now().toString(36)}-${(this.seq).toString(36)}`,\n startedAt: typeof performance !== \"undefined\" ? performance.now() : Date.now(),\n startedWall: Date.now(),\n stoppedAt: null,\n events: [],\n limit,\n };\n this.recording = rec;\n this.recordings.set(rec.id, rec);\n // Keep only the 5 most recent recordings to bound memory.\n while (this.recordings.size > 5) {\n const oldest = this.recordings.keys().next().value;\n if (oldest === undefined) break;\n this.recordings.delete(oldest);\n }\n return rec;\n }\n\n stopRecording(): Recording | null {\n const rec = this.recording;\n if (!rec) return null;\n rec.stoppedAt = typeof performance !== \"undefined\" ? performance.now() : Date.now();\n this.recording = null;\n return rec;\n }\n\n currentRecording() {\n return this.recording;\n }\n\n getRecording(id: string) {\n return this.recordings.get(id) ?? null;\n }\n\n listRecordings() {\n return [...this.recordings.values()].map((r) => ({\n id: r.id,\n startedWall: r.startedWall,\n stoppedAt: r.stoppedAt,\n events: r.events.length,\n active: r === this.recording,\n }));\n }\n}\n","/**\n * Event model. Every observation the runtime makes is one PulseEvent with a\n * precise `kind`. Kinds are deliberately specific: Solid has no \"rerender\", so\n * we never report one. What actually happens is one of:\n *\n * solid.flush a reactive flush completed (n computations re-ran)\n * solid.computation one memo/effect/render-effect re-ran (verbose mode)\n * solid.component.mount a component function ran (fresh mount, or hydrate)\n * solid.component.dispose\n * solid.component.remount same-named component disposed + mounted in one flush\n * solid.root a reactive root was created (multiple roots are fine)\n * dom.mutation the DOM actually changed (childList/attr/text)\n * dom.detach a subtree left the document (with scroll/focus state)\n * dom.reattach the same node instance came back (Suspense flip etc.)\n * focus.lost the focused element vanished from the document\n * net.fetch.* fetch lifecycle (start/end/error), SSE-aware\n * net.ws.* WebSocket lifecycle (open/message/close/error)\n * net.sse.* EventSource lifecycle\n * query.* Solid Query cache/observer events (adapter)\n * mutation.* Solid Query mutation cache events (adapter)\n * pulse.* runtime lifecycle / control-plane notes\n */\n\nexport type PulseEventKind =\n | \"solid.flush\"\n | \"solid.computation\"\n | \"solid.component.mount\"\n | \"solid.component.dispose\"\n | \"solid.component.remount\"\n | \"solid.root\"\n | \"dom.mutation\"\n | \"dom.detach\"\n | \"dom.reattach\"\n | \"focus.lost\"\n | \"net.fetch.start\"\n | \"net.fetch.end\"\n | \"net.fetch.error\"\n | \"net.ws.open\"\n | \"net.ws.message\"\n | \"net.ws.close\"\n | \"net.ws.error\"\n | \"net.sse.open\"\n | \"net.sse.message\"\n | \"net.sse.error\"\n | \"net.sse.close\"\n | \"query.added\"\n | \"query.removed\"\n | \"query.observe\"\n | \"query.unobserve\"\n | \"query.fetch.start\"\n | \"query.fetch.success\"\n | \"query.fetch.error\"\n | \"query.invalidate\"\n | \"query.update\"\n | \"mutation.start\"\n | \"mutation.success\"\n | \"mutation.error\"\n | \"pulse.note\";\n\nexport interface Rect {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\nexport interface ComponentRef {\n id: number;\n name: string;\n /** Component ancestry, innermost first (names only). */\n chain?: string[];\n /** Source location if solid-grab's data-solid-source attribute was found. */\n source?: string | null;\n}\n\nexport interface ElementRef {\n tag: string;\n id?: string;\n testId?: string;\n classes?: string;\n /** Nearest `data-solid-component` ancestor name, if any. */\n component?: string | null;\n /** Nearest `data-solid-source` value, if any. */\n source?: string | null;\n rect?: Rect;\n}\n\nexport interface PulseEventBase {\n /** Monotonic per-runtime sequence. */\n seq: number;\n /** performance.now() at capture. */\n t: number;\n /** Date.now() at capture (for cross-process correlation). */\n wall: number;\n kind: PulseEventKind;\n /** Flush group the event belongs to, when attributable. */\n flush?: number;\n /** Component attribution, when known. */\n component?: ComponentRef | null;\n /** Free-form structured payload; shape depends on `kind`. */\n data: Record<string, unknown>;\n}\n\nexport type PulseEvent = PulseEventBase;\n\nexport const KIND_GROUPS: Record<string, PulseEventKind[]> = {\n solid: [\n \"solid.flush\",\n \"solid.computation\",\n \"solid.component.mount\",\n \"solid.component.dispose\",\n \"solid.component.remount\",\n \"solid.root\",\n ],\n dom: [\"dom.mutation\", \"dom.detach\", \"dom.reattach\", \"focus.lost\"],\n net: [\n \"net.fetch.start\",\n \"net.fetch.end\",\n \"net.fetch.error\",\n \"net.ws.open\",\n \"net.ws.message\",\n \"net.ws.close\",\n \"net.ws.error\",\n \"net.sse.open\",\n \"net.sse.message\",\n \"net.sse.error\",\n \"net.sse.close\",\n ],\n query: [\n \"query.added\",\n \"query.removed\",\n \"query.observe\",\n \"query.unobserve\",\n \"query.fetch.start\",\n \"query.fetch.success\",\n \"query.fetch.error\",\n \"query.invalidate\",\n \"query.update\",\n \"mutation.start\",\n \"mutation.success\",\n \"mutation.error\",\n ],\n pulse: [\"pulse.note\"],\n};\n\nexport const ALL_KINDS: PulseEventKind[] = Object.values(KIND_GROUPS).flat();\n\n/** Expand a kind filter: exact kinds, `group` names, or `prefix.*` globs. */\nexport function expandKinds(filters: readonly string[]): Set<PulseEventKind> {\n const out = new Set<PulseEventKind>();\n for (const raw of filters) {\n const f = raw.trim();\n if (!f) continue;\n if (f === \"*\" || f === \"all\") {\n for (const k of ALL_KINDS) out.add(k);\n continue;\n }\n const group = KIND_GROUPS[f];\n if (group) {\n for (const k of group) out.add(k);\n continue;\n }\n if (f.endsWith(\"*\")) {\n const prefix = f.slice(0, -1);\n for (const k of ALL_KINDS) if (k.startsWith(prefix)) out.add(k);\n continue;\n }\n if ((ALL_KINDS as string[]).includes(f)) out.add(f as PulseEventKind);\n }\n return out;\n}\n","import { EventBus } from \"./bus.js\";\nimport { expandKinds, type PulseEvent, type PulseEventKind } from \"./events.js\";\n\n/** Runtime features that can be toggled by humans (panel) and agents (CLI) alike. */\nexport type Feature =\n | \"solid\"\n | \"dom\"\n | \"flash\"\n | \"network\"\n | \"query\"\n | \"queryOverlay\"\n | \"verboseComputations\"\n | \"captureBodies\";\n\nexport const FEATURES: Feature[] = [\n \"solid\",\n \"dom\",\n \"flash\",\n \"network\",\n \"query\",\n \"queryOverlay\",\n \"verboseComputations\",\n \"captureBodies\",\n];\n\nexport interface CommandSpec {\n /** Dotted command name, e.g. `features.set`. Also the CLI verb. */\n name: string;\n summary: string;\n /** Argument name → short description. */\n args?: Record<string, string>;\n /** Where the equivalent human control lives in the panel (parity doc). */\n ui?: string;\n}\n\nexport type CommandHandler = (args: Record<string, unknown>) => unknown | Promise<unknown>;\n\nexport type CommandResult =\n | { ok: true; value: unknown }\n | { ok: false; error: string };\n\nexport interface Filters {\n /** Kind filters (exact kinds, groups, or `prefix.*`); empty = all. */\n kinds: string[];\n /** Case-insensitive substring on component name. */\n component: string;\n /** Case-insensitive substring on any string in `data` (URLs, keys). */\n text: string;\n}\n\nexport type FeatureListener = (feature: Feature, on: boolean) => void;\n\n/**\n * The single command surface. The panel's buttons, the bridge's HTTP API and\n * the CLI all call `run()`, so parity between human and agent control is a\n * property of the design rather than a checklist. `describe()` is the\n * machine-readable contract the CLI prints and the parity test asserts on.\n */\nexport class PulseController {\n readonly bus: EventBus;\n private commands = new Map<string, { spec: CommandSpec; handler: CommandHandler }>();\n private features: Record<Feature, boolean>;\n private featureListeners = new Set<FeatureListener>();\n filters: Filters = { kinds: [], component: \"\", text: \"\" };\n private filterListeners = new Set<(f: Filters) => void>();\n readonly startedWall = Date.now();\n\n constructor(bus = new EventBus(), initial: Partial<Record<Feature, boolean>> = {}) {\n this.bus = bus;\n this.features = {\n solid: true,\n dom: true,\n flash: true,\n network: true,\n query: true,\n queryOverlay: true,\n verboseComputations: false,\n captureBodies: false,\n ...initial,\n };\n this.registerCore();\n }\n\n // ── features ─────────────────────────────────────────────────────\n\n isOn(feature: Feature) {\n return this.features[feature];\n }\n\n setFeature(feature: Feature, on: boolean) {\n if (!FEATURES.includes(feature)) throw new Error(`unknown feature: ${feature}`);\n if (this.features[feature] === on) return;\n this.features[feature] = on;\n for (const l of this.featureListeners) l(feature, on);\n this.bus.emit(\"pulse.note\", { note: `feature ${feature} ${on ? \"on\" : \"off\"}` });\n }\n\n onFeature(listener: FeatureListener) {\n this.featureListeners.add(listener);\n return () => this.featureListeners.delete(listener);\n }\n\n snapshotFeatures() {\n return { ...this.features };\n }\n\n // ── filters ──────────────────────────────────────────────────────\n\n setFilters(next: Partial<Filters>) {\n this.filters = { ...this.filters, ...next };\n for (const l of this.filterListeners) l(this.filters);\n }\n\n onFilters(listener: (f: Filters) => void) {\n this.filterListeners.add(listener);\n return () => this.filterListeners.delete(listener);\n }\n\n matchesFilters(e: PulseEvent, f: Filters = this.filters): boolean {\n if (f.kinds.length) {\n const kinds = expandKinds(f.kinds);\n if (kinds.size && !kinds.has(e.kind)) return false;\n }\n if (f.component) {\n const name = e.component?.name ?? \"\";\n const chain = e.component?.chain?.join(\" \") ?? \"\";\n const needle = f.component.toLowerCase();\n if (!name.toLowerCase().includes(needle) && !chain.toLowerCase().includes(needle)) return false;\n }\n if (f.text) {\n const needle = f.text.toLowerCase();\n if (!JSON.stringify(e.data).toLowerCase().includes(needle)) return false;\n }\n return true;\n }\n\n /** Buffered events after `since`, through the active filters. */\n events(opts: { since?: number; limit?: number; kinds?: string[]; raw?: boolean } = {}) {\n const kinds = opts.kinds ? expandKinds(opts.kinds) : null;\n const list = this.bus.list({ since: opts.since, limit: opts.raw ? opts.limit : 5000, kinds });\n const filtered = opts.raw ? list : list.filter((e) => this.matchesFilters(e));\n const limit = opts.limit ?? 500;\n return filtered.length > limit ? filtered.slice(filtered.length - limit) : filtered;\n }\n\n // ── commands ─────────────────────────────────────────────────────\n\n register(spec: CommandSpec, handler: CommandHandler) {\n if (this.commands.has(spec.name)) throw new Error(`command already registered: ${spec.name}`);\n this.commands.set(spec.name, { spec, handler });\n }\n\n unregister(name: string) {\n this.commands.delete(name);\n }\n\n has(name: string) {\n return this.commands.has(name);\n }\n\n describe(): CommandSpec[] {\n return [...this.commands.values()].map((c) => c.spec).sort((a, b) => a.name.localeCompare(b.name));\n }\n\n async run(name: string, args: Record<string, unknown> = {}): Promise<CommandResult> {\n const cmd = this.commands.get(name);\n if (!cmd) return { ok: false, error: `unknown command: ${name}` };\n try {\n const value = await cmd.handler(args ?? {});\n return { ok: true, value: value === undefined ? null : value };\n } catch (err) {\n return { ok: false, error: err instanceof Error ? err.message : String(err) };\n }\n }\n\n private registerCore() {\n this.register(\n { name: \"status\", summary: \"Runtime status: features, filters, buffer stats, recording.\", ui: \"panel header\" },\n () => ({\n startedWall: this.startedWall,\n features: this.snapshotFeatures(),\n filters: this.filters,\n buffer: { size: this.bus.buffer.size, capacity: this.bus.buffer.capacity, dropped: this.bus.buffer.dropped, seq: this.bus.nextSeq - 1 },\n paused: this.bus.paused,\n recording: this.bus.currentRecording()?.id ?? null,\n commands: this.commands.size,\n }),\n );\n this.register(\n { name: \"commands\", summary: \"List every command with its panel equivalent (the parity contract).\" },\n () => this.describe(),\n );\n this.register(\n { name: \"features.list\", summary: \"Feature flags and their current state.\", ui: \"Pulse tab › toggles\" },\n () => this.snapshotFeatures(),\n );\n this.register(\n {\n name: \"features.set\",\n summary: \"Turn a feature on/off.\",\n args: { name: FEATURES.join(\"|\"), on: \"true|false (default true)\" },\n ui: \"Pulse tab › toggles\",\n },\n (a) => {\n const name = String(a.name ?? \"\") as Feature;\n const on = a.on === undefined ? true : a.on === true || a.on === \"true\" || a.on === \"on\" || a.on === 1;\n this.setFeature(name, on);\n return this.snapshotFeatures();\n },\n );\n this.register(\n {\n name: \"filters.set\",\n summary: \"Set event filters (kinds list, component substring, text substring). Empty string clears.\",\n args: { kinds: \"comma list: solid,dom,net,query or kinds/globs\", component: \"substring\", text: \"substring\" },\n ui: \"Pulse tab › filter bar\",\n },\n (a) => {\n const next: Partial<Filters> = {};\n if (a.kinds !== undefined) {\n next.kinds = Array.isArray(a.kinds)\n ? (a.kinds as unknown[]).map(String)\n : String(a.kinds).split(\",\").map((s) => s.trim()).filter(Boolean);\n }\n if (a.component !== undefined) next.component = String(a.component);\n if (a.text !== undefined) next.text = String(a.text);\n this.setFilters(next);\n return this.filters;\n },\n );\n this.register({ name: \"filters.get\", summary: \"Current filters.\", ui: \"Pulse tab › filter bar\" }, () => this.filters);\n this.register(\n {\n name: \"events.list\",\n summary: \"Buffered events (oldest first) through the active filters.\",\n args: { since: \"seq to start after\", limit: \"max rows (default 200)\", kinds: \"override kind filter\", raw: \"true = ignore filters\" },\n ui: \"Pulse tab › event list\",\n },\n (a) => {\n const kinds = a.kinds === undefined ? undefined : String(a.kinds).split(\",\").map((s) => s.trim()).filter(Boolean);\n return this.events({\n since: a.since === undefined ? undefined : Number(a.since),\n limit: a.limit === undefined ? 200 : Number(a.limit),\n kinds,\n raw: a.raw === true || a.raw === \"true\",\n });\n },\n );\n this.register({ name: \"events.clear\", summary: \"Clear the event buffer.\", ui: \"Pulse tab › Clear\" }, () => {\n this.bus.clear();\n return { cleared: true };\n });\n this.register({ name: \"events.pause\", summary: \"Stop capturing new events (buffer kept).\", ui: \"Pulse tab › Pause\" }, () => {\n this.bus.paused = true;\n return { paused: true };\n });\n this.register({ name: \"events.resume\", summary: \"Resume capturing.\", ui: \"Pulse tab › Pause\" }, () => {\n this.bus.paused = false;\n return { paused: false };\n });\n this.register(\n { name: \"note\", summary: \"Append a marker note to the timeline (agents: label a QA step).\", args: { text: \"note text\" }, ui: \"Record tab › Note\" },\n (a) => this.bus.emit(\"pulse.note\", { note: String(a.text ?? \"\") }),\n );\n this.register(\n { name: \"record.start\", summary: \"Start a recording (stops any active one).\", args: { id: \"optional id\", limit: \"max events (default 20000)\" }, ui: \"Record tab › Start\" },\n (a) => {\n const rec = this.bus.startRecording(a.id === undefined ? undefined : String(a.id), a.limit === undefined ? undefined : Number(a.limit));\n return { id: rec.id, startedWall: rec.startedWall };\n },\n );\n this.register({ name: \"record.stop\", summary: \"Stop the active recording.\", ui: \"Record tab › Stop\" }, () => {\n const rec = this.bus.stopRecording();\n return rec ? { id: rec.id, events: rec.events.length } : null;\n });\n this.register({ name: \"record.list\", summary: \"Recordings kept in memory (last 5).\", ui: \"Record tab › list\" }, () => this.bus.listRecordings());\n this.register(\n {\n name: \"export\",\n summary: \"Export a recording (or the live buffer) as JSON: {meta, events}.\",\n args: { recording: \"recording id; omit for the live buffer\", filtered: \"true = apply filters to the live buffer\" },\n ui: \"Record tab › Export\",\n },\n (a) => {\n const id = a.recording === undefined ? null : String(a.recording);\n const rec = id ? this.bus.getRecording(id) : null;\n if (id && !rec) throw new Error(`unknown recording: ${id}`);\n const events = rec ? rec.events : a.filtered === true || a.filtered === \"true\" ? this.events({ limit: 100_000 }) : this.bus.list({ limit: 100_000 });\n return {\n meta: {\n tool: \"@omniaura/solid-pulse\",\n exportedWall: Date.now(),\n recording: rec ? { id: rec.id, startedWall: rec.startedWall, stoppedAt: rec.stoppedAt } : null,\n features: this.snapshotFeatures(),\n filters: this.filters,\n count: events.length,\n },\n events,\n };\n },\n );\n }\n}\n\nexport type { PulseEvent, PulseEventKind };\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// src/overlay/flash.ts
|
|
2
|
+
var COLORS = {
|
|
3
|
+
dom: "245,158,11",
|
|
4
|
+
mount: "34,197,94",
|
|
5
|
+
reattach: "239,68,68",
|
|
6
|
+
query: "59,130,246",
|
|
7
|
+
highlight: "168,85,247"
|
|
8
|
+
};
|
|
9
|
+
var OWN_ATTR = "data-solid-pulse";
|
|
10
|
+
var MAX_LIVE_RECTS = 48;
|
|
11
|
+
var MAX_LIVE_BADGES = 4;
|
|
12
|
+
var FlashOverlay = class {
|
|
13
|
+
root = null;
|
|
14
|
+
live = 0;
|
|
15
|
+
badges = 0;
|
|
16
|
+
reduced = false;
|
|
17
|
+
mount() {
|
|
18
|
+
if (this.root || typeof document === "undefined") return;
|
|
19
|
+
const root = document.createElement("div");
|
|
20
|
+
root.setAttribute(OWN_ATTR, "overlay");
|
|
21
|
+
root.setAttribute("aria-hidden", "true");
|
|
22
|
+
root.style.cssText = "position:fixed;inset:0;pointer-events:none;z-index:2147483646;contain:strict;overflow:hidden;font:12px/1.3 ui-monospace,SFMono-Regular,Menlo,monospace;";
|
|
23
|
+
const style = document.createElement("style");
|
|
24
|
+
style.textContent = `
|
|
25
|
+
@keyframes sp-flash{0%{opacity:.85}100%{opacity:0}}
|
|
26
|
+
@keyframes sp-badge{0%{opacity:0;transform:translate(-50%,-50%) scale(.96)}12%{opacity:1;transform:translate(-50%,-50%) scale(1)}75%{opacity:1}100%{opacity:0}}
|
|
27
|
+
[${OWN_ATTR}="rect"]{position:absolute;box-sizing:border-box;border-radius:3px;animation:sp-flash var(--ms,600ms) ease-out forwards}
|
|
28
|
+
[${OWN_ATTR}="badge"]{position:absolute;max-width:min(60vw,560px);padding:6px 10px;border-radius:8px;color:#fff;background:rgba(17,24,39,.92);box-shadow:0 4px 18px rgba(0,0,0,.35);white-space:pre-wrap;word-break:break-all;animation:sp-badge var(--ms,1200ms) ease-out forwards}
|
|
29
|
+
[${OWN_ATTR}="badge"] b{font-weight:600}
|
|
30
|
+
`;
|
|
31
|
+
root.appendChild(style);
|
|
32
|
+
(document.body ?? document.documentElement).appendChild(root);
|
|
33
|
+
this.root = root;
|
|
34
|
+
try {
|
|
35
|
+
this.reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
|
|
36
|
+
} catch {
|
|
37
|
+
this.reduced = false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
unmount() {
|
|
41
|
+
this.root?.remove();
|
|
42
|
+
this.root = null;
|
|
43
|
+
this.live = 0;
|
|
44
|
+
this.badges = 0;
|
|
45
|
+
}
|
|
46
|
+
get mounted() {
|
|
47
|
+
return this.root !== null;
|
|
48
|
+
}
|
|
49
|
+
/** Flash rectangles in viewport coordinates. Drops extras beyond the live cap. */
|
|
50
|
+
flash(rects, kind, opts = {}) {
|
|
51
|
+
if (!this.root) return 0;
|
|
52
|
+
const ms = this.reduced ? Math.min(opts.ms ?? 600, 250) : opts.ms ?? 600;
|
|
53
|
+
const color = COLORS[kind];
|
|
54
|
+
let drawn = 0;
|
|
55
|
+
for (const r of rects) {
|
|
56
|
+
if (this.live >= MAX_LIVE_RECTS) break;
|
|
57
|
+
if (r.w <= 0 || r.h <= 0) continue;
|
|
58
|
+
const el = document.createElement("div");
|
|
59
|
+
el.setAttribute(OWN_ATTR, "rect");
|
|
60
|
+
el.style.cssText = `left:${r.x}px;top:${r.y}px;width:${r.w}px;height:${r.h}px;background:rgba(${color},.18);border:2px solid rgba(${color},.9);--ms:${ms}ms`;
|
|
61
|
+
if (opts.label && drawn === 0) {
|
|
62
|
+
const tag = document.createElement("span");
|
|
63
|
+
tag.textContent = opts.label;
|
|
64
|
+
tag.style.cssText = `position:absolute;left:-2px;top:-18px;padding:1px 5px;border-radius:3px;color:#fff;background:rgba(${color},.95);font-size:10px;white-space:nowrap`;
|
|
65
|
+
el.appendChild(tag);
|
|
66
|
+
}
|
|
67
|
+
this.root.appendChild(el);
|
|
68
|
+
this.live++;
|
|
69
|
+
drawn++;
|
|
70
|
+
setTimeout(() => {
|
|
71
|
+
el.remove();
|
|
72
|
+
this.live--;
|
|
73
|
+
}, ms + 20);
|
|
74
|
+
}
|
|
75
|
+
return drawn;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Transient badge centred on `at` (or the viewport when null). Used for the
|
|
79
|
+
* Solid Query overlay: "<Component> observing ['todos', 1]".
|
|
80
|
+
*/
|
|
81
|
+
badge(html, at, opts = {}) {
|
|
82
|
+
if (!this.root) return false;
|
|
83
|
+
if (this.badges >= MAX_LIVE_BADGES) return false;
|
|
84
|
+
const ms = this.reduced ? Math.min(opts.ms ?? 1200, 600) : opts.ms ?? 1200;
|
|
85
|
+
const color = COLORS[opts.kind ?? "query"];
|
|
86
|
+
const vw = window.innerWidth;
|
|
87
|
+
const vh = window.innerHeight;
|
|
88
|
+
const cx = at ? Math.min(Math.max(at.x + at.w / 2, 80), vw - 80) : vw / 2;
|
|
89
|
+
const cy = at ? Math.min(Math.max(at.y + at.h / 2, 30), vh - 30) : vh / 2;
|
|
90
|
+
const el = document.createElement("div");
|
|
91
|
+
el.setAttribute(OWN_ATTR, "badge");
|
|
92
|
+
el.style.cssText = `left:${cx}px;top:${cy + this.badges * 34}px;border-left:4px solid rgb(${color});--ms:${ms}ms`;
|
|
93
|
+
const b = document.createElement("b");
|
|
94
|
+
b.textContent = html.title;
|
|
95
|
+
el.appendChild(b);
|
|
96
|
+
if (html.body) {
|
|
97
|
+
el.appendChild(document.createElement("br"));
|
|
98
|
+
el.appendChild(document.createTextNode(html.body));
|
|
99
|
+
}
|
|
100
|
+
this.root.appendChild(el);
|
|
101
|
+
this.badges++;
|
|
102
|
+
setTimeout(() => {
|
|
103
|
+
el.remove();
|
|
104
|
+
this.badges--;
|
|
105
|
+
}, ms + 20);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export {
|
|
111
|
+
OWN_ATTR,
|
|
112
|
+
FlashOverlay
|
|
113
|
+
};
|
|
114
|
+
//# sourceMappingURL=chunk-IXNWEUNF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/overlay/flash.ts"],"sourcesContent":["/**\n * Visual layer. One fixed, pointer-events:none container holds every flash\n * rectangle and badge, so the overlay can never shift layout, take focus or\n * intercept input. Everything is plain DOM (no Solid) so it produces no\n * reactive events of its own and works with any Solid version.\n */\n\nimport type { Rect } from \"../core/events.js\";\n\nexport type FlashKind = \"dom\" | \"mount\" | \"reattach\" | \"query\" | \"highlight\";\n\nconst COLORS: Record<FlashKind, string> = {\n dom: \"245,158,11\",\n mount: \"34,197,94\",\n reattach: \"239,68,68\",\n query: \"59,130,246\",\n highlight: \"168,85,247\",\n};\n\nexport const OWN_ATTR = \"data-solid-pulse\";\n\nconst MAX_LIVE_RECTS = 48;\nconst MAX_LIVE_BADGES = 4;\n\nexport class FlashOverlay {\n private root: HTMLDivElement | null = null;\n private live = 0;\n private badges = 0;\n private reduced = false;\n\n mount() {\n if (this.root || typeof document === \"undefined\") return;\n const root = document.createElement(\"div\");\n root.setAttribute(OWN_ATTR, \"overlay\");\n root.setAttribute(\"aria-hidden\", \"true\");\n root.style.cssText =\n \"position:fixed;inset:0;pointer-events:none;z-index:2147483646;contain:strict;overflow:hidden;font:12px/1.3 ui-monospace,SFMono-Regular,Menlo,monospace;\";\n const style = document.createElement(\"style\");\n style.textContent = `\n@keyframes sp-flash{0%{opacity:.85}100%{opacity:0}}\n@keyframes sp-badge{0%{opacity:0;transform:translate(-50%,-50%) scale(.96)}12%{opacity:1;transform:translate(-50%,-50%) scale(1)}75%{opacity:1}100%{opacity:0}}\n[${OWN_ATTR}=\"rect\"]{position:absolute;box-sizing:border-box;border-radius:3px;animation:sp-flash var(--ms,600ms) ease-out forwards}\n[${OWN_ATTR}=\"badge\"]{position:absolute;max-width:min(60vw,560px);padding:6px 10px;border-radius:8px;color:#fff;background:rgba(17,24,39,.92);box-shadow:0 4px 18px rgba(0,0,0,.35);white-space:pre-wrap;word-break:break-all;animation:sp-badge var(--ms,1200ms) ease-out forwards}\n[${OWN_ATTR}=\"badge\"] b{font-weight:600}\n`;\n root.appendChild(style);\n (document.body ?? document.documentElement).appendChild(root);\n this.root = root;\n try {\n this.reduced = window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches ?? false;\n } catch {\n this.reduced = false;\n }\n }\n\n unmount() {\n this.root?.remove();\n this.root = null;\n this.live = 0;\n this.badges = 0;\n }\n\n get mounted() {\n return this.root !== null;\n }\n\n /** Flash rectangles in viewport coordinates. Drops extras beyond the live cap. */\n flash(rects: readonly Rect[], kind: FlashKind, opts: { ms?: number; label?: string } = {}) {\n if (!this.root) return 0;\n const ms = this.reduced ? Math.min(opts.ms ?? 600, 250) : (opts.ms ?? 600);\n const color = COLORS[kind];\n let drawn = 0;\n for (const r of rects) {\n if (this.live >= MAX_LIVE_RECTS) break;\n if (r.w <= 0 || r.h <= 0) continue;\n const el = document.createElement(\"div\");\n el.setAttribute(OWN_ATTR, \"rect\");\n el.style.cssText = `left:${r.x}px;top:${r.y}px;width:${r.w}px;height:${r.h}px;background:rgba(${color},.18);border:2px solid rgba(${color},.9);--ms:${ms}ms`;\n if (opts.label && drawn === 0) {\n const tag = document.createElement(\"span\");\n tag.textContent = opts.label;\n tag.style.cssText = `position:absolute;left:-2px;top:-18px;padding:1px 5px;border-radius:3px;color:#fff;background:rgba(${color},.95);font-size:10px;white-space:nowrap`;\n el.appendChild(tag);\n }\n this.root.appendChild(el);\n this.live++;\n drawn++;\n setTimeout(() => {\n el.remove();\n this.live--;\n }, ms + 20);\n }\n return drawn;\n }\n\n /**\n * Transient badge centred on `at` (or the viewport when null). Used for the\n * Solid Query overlay: \"<Component> observing ['todos', 1]\".\n */\n badge(html: { title: string; body?: string }, at: Rect | null, opts: { ms?: number; kind?: FlashKind } = {}) {\n if (!this.root) return false;\n if (this.badges >= MAX_LIVE_BADGES) return false;\n const ms = this.reduced ? Math.min(opts.ms ?? 1200, 600) : (opts.ms ?? 1200);\n const color = COLORS[opts.kind ?? \"query\"];\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n const cx = at ? Math.min(Math.max(at.x + at.w / 2, 80), vw - 80) : vw / 2;\n const cy = at ? Math.min(Math.max(at.y + at.h / 2, 30), vh - 30) : vh / 2;\n const el = document.createElement(\"div\");\n el.setAttribute(OWN_ATTR, \"badge\");\n el.style.cssText = `left:${cx}px;top:${cy + this.badges * 34}px;border-left:4px solid rgb(${color});--ms:${ms}ms`;\n const b = document.createElement(\"b\");\n b.textContent = html.title;\n el.appendChild(b);\n if (html.body) {\n el.appendChild(document.createElement(\"br\"));\n el.appendChild(document.createTextNode(html.body));\n }\n this.root.appendChild(el);\n this.badges++;\n setTimeout(() => {\n el.remove();\n this.badges--;\n }, ms + 20);\n return true;\n }\n}\n"],"mappings":";AAWA,IAAM,SAAoC;AAAA,EACxC,KAAK;AAAA,EACL,OAAO;AAAA,EACP,UAAU;AAAA,EACV,OAAO;AAAA,EACP,WAAW;AACb;AAEO,IAAM,WAAW;AAExB,IAAM,iBAAiB;AACvB,IAAM,kBAAkB;AAEjB,IAAM,eAAN,MAAmB;AAAA,EAChB,OAA8B;AAAA,EAC9B,OAAO;AAAA,EACP,SAAS;AAAA,EACT,UAAU;AAAA,EAElB,QAAQ;AACN,QAAI,KAAK,QAAQ,OAAO,aAAa,YAAa;AAClD,UAAM,OAAO,SAAS,cAAc,KAAK;AACzC,SAAK,aAAa,UAAU,SAAS;AACrC,SAAK,aAAa,eAAe,MAAM;AACvC,SAAK,MAAM,UACT;AACF,UAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,UAAM,cAAc;AAAA;AAAA;AAAA,GAGrB,QAAQ;AAAA,GACR,QAAQ;AAAA,GACR,QAAQ;AAAA;AAEP,SAAK,YAAY,KAAK;AACtB,KAAC,SAAS,QAAQ,SAAS,iBAAiB,YAAY,IAAI;AAC5D,SAAK,OAAO;AACZ,QAAI;AACF,WAAK,UAAU,OAAO,aAAa,kCAAkC,EAAE,WAAW;AAAA,IACpF,QAAQ;AACN,WAAK,UAAU;AAAA,IACjB;AAAA,EACF;AAAA,EAEA,UAAU;AACR,SAAK,MAAM,OAAO;AAClB,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,IAAI,UAAU;AACZ,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA;AAAA,EAGA,MAAM,OAAwB,MAAiB,OAAwC,CAAC,GAAG;AACzF,QAAI,CAAC,KAAK,KAAM,QAAO;AACvB,UAAM,KAAK,KAAK,UAAU,KAAK,IAAI,KAAK,MAAM,KAAK,GAAG,IAAK,KAAK,MAAM;AACtE,UAAM,QAAQ,OAAO,IAAI;AACzB,QAAI,QAAQ;AACZ,eAAW,KAAK,OAAO;AACrB,UAAI,KAAK,QAAQ,eAAgB;AACjC,UAAI,EAAE,KAAK,KAAK,EAAE,KAAK,EAAG;AAC1B,YAAM,KAAK,SAAS,cAAc,KAAK;AACvC,SAAG,aAAa,UAAU,MAAM;AAChC,SAAG,MAAM,UAAU,QAAQ,EAAE,CAAC,UAAU,EAAE,CAAC,YAAY,EAAE,CAAC,aAAa,EAAE,CAAC,sBAAsB,KAAK,+BAA+B,KAAK,aAAa,EAAE;AACxJ,UAAI,KAAK,SAAS,UAAU,GAAG;AAC7B,cAAM,MAAM,SAAS,cAAc,MAAM;AACzC,YAAI,cAAc,KAAK;AACvB,YAAI,MAAM,UAAU,sGAAsG,KAAK;AAC/H,WAAG,YAAY,GAAG;AAAA,MACpB;AACA,WAAK,KAAK,YAAY,EAAE;AACxB,WAAK;AACL;AACA,iBAAW,MAAM;AACf,WAAG,OAAO;AACV,aAAK;AAAA,MACP,GAAG,KAAK,EAAE;AAAA,IACZ;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,MAAwC,IAAiB,OAA0C,CAAC,GAAG;AAC3G,QAAI,CAAC,KAAK,KAAM,QAAO;AACvB,QAAI,KAAK,UAAU,gBAAiB,QAAO;AAC3C,UAAM,KAAK,KAAK,UAAU,KAAK,IAAI,KAAK,MAAM,MAAM,GAAG,IAAK,KAAK,MAAM;AACvE,UAAM,QAAQ,OAAO,KAAK,QAAQ,OAAO;AACzC,UAAM,KAAK,OAAO;AAClB,UAAM,KAAK,OAAO;AAClB,UAAM,KAAK,KAAK,KAAK,IAAI,KAAK,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,EAAE,GAAG,KAAK,EAAE,IAAI,KAAK;AACxE,UAAM,KAAK,KAAK,KAAK,IAAI,KAAK,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,EAAE,GAAG,KAAK,EAAE,IAAI,KAAK;AACxE,UAAM,KAAK,SAAS,cAAc,KAAK;AACvC,OAAG,aAAa,UAAU,OAAO;AACjC,OAAG,MAAM,UAAU,QAAQ,EAAE,UAAU,KAAK,KAAK,SAAS,EAAE,gCAAgC,KAAK,UAAU,EAAE;AAC7G,UAAM,IAAI,SAAS,cAAc,GAAG;AACpC,MAAE,cAAc,KAAK;AACrB,OAAG,YAAY,CAAC;AAChB,QAAI,KAAK,MAAM;AACb,SAAG,YAAY,SAAS,cAAc,IAAI,CAAC;AAC3C,SAAG,YAAY,SAAS,eAAe,KAAK,IAAI,CAAC;AAAA,IACnD;AACA,SAAK,KAAK,YAAY,EAAE;AACxB,SAAK;AACL,eAAW,MAAM;AACf,SAAG,OAAO;AACV,WAAK;AAAA,IACP,GAAG,KAAK,EAAE;AACV,WAAO;AAAA,EACT;AACF;","names":[]}
|