devlog-ui 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/dist/index.js ADDED
@@ -0,0 +1,3529 @@
1
+ var Me = Object.defineProperty;
2
+ var Be = (t, e, n) => e in t ? Me(t, e, { enumerable: !0, configurable: !0, writable: !0, value: n }) : t[e] = n;
3
+ var p = (t, e, n) => Be(t, typeof e != "symbol" ? e + "" : e, n);
4
+ const se = {
5
+ debug: 0,
6
+ info: 1,
7
+ warn: 2,
8
+ error: 3
9
+ };
10
+ function w(t) {
11
+ return t !== null && typeof t == "object" && !Array.isArray(t);
12
+ }
13
+ function F(t, e) {
14
+ if (t === e) return !0;
15
+ if (typeof t != typeof e) return !1;
16
+ if (t === null || e === null) return t === e;
17
+ if (Array.isArray(t) && Array.isArray(e))
18
+ return t.length !== e.length ? !1 : t.every((n, o) => F(n, e[o]));
19
+ if (w(t) && w(e)) {
20
+ const n = Object.keys(t), o = Object.keys(e);
21
+ return n.length !== o.length ? !1 : n.every((r) => F(t[r], e[r]));
22
+ }
23
+ return !1;
24
+ }
25
+ function ae(t) {
26
+ if (t === void 0) return "undefined";
27
+ if (t === null) return "null";
28
+ if (typeof t == "string") return `"${t}"`;
29
+ if (typeof t == "number" || typeof t == "boolean") return String(t);
30
+ if (Array.isArray(t))
31
+ return t.length === 0 ? "[]" : t.length <= 3 ? `[${t.map(ae).join(", ")}]` : `[${t.length} items]`;
32
+ if (w(t)) {
33
+ const e = Object.keys(t);
34
+ return e.length === 0 ? "{}" : e.length <= 2 ? `{${e.map((n) => `${n}: ${ae(t[n])}`).join(", ")}}` : `{${e.length} keys}`;
35
+ }
36
+ return String(t);
37
+ }
38
+ function ve(t, e, n = "", o = !1) {
39
+ const r = [];
40
+ if (!w(t) && !w(e))
41
+ return F(t, e) ? o && r.push({
42
+ path: n || "(root)",
43
+ type: "unchanged",
44
+ oldValue: t,
45
+ newValue: e
46
+ }) : r.push({
47
+ path: n || "(root)",
48
+ type: "changed",
49
+ oldValue: t,
50
+ newValue: e
51
+ }), r;
52
+ if (!w(t))
53
+ return r.push({
54
+ path: n || "(root)",
55
+ type: "changed",
56
+ oldValue: t,
57
+ newValue: e
58
+ }), r;
59
+ if (!w(e))
60
+ return r.push({
61
+ path: n || "(root)",
62
+ type: "changed",
63
+ oldValue: t,
64
+ newValue: e
65
+ }), r;
66
+ const s = /* @__PURE__ */ new Set([...Object.keys(t), ...Object.keys(e)]);
67
+ for (const c of s) {
68
+ const l = n ? `${n}.${c}` : c, a = t[c], d = e[c], f = c in t, h = c in e;
69
+ !f && h ? r.push({
70
+ path: l,
71
+ type: "added",
72
+ newValue: d
73
+ }) : f && !h ? r.push({
74
+ path: l,
75
+ type: "removed",
76
+ oldValue: a
77
+ }) : w(a) && w(d) ? r.push(...ve(a, d, l, o)) : Array.isArray(a) && Array.isArray(d) ? F(a, d) ? o && r.push({
78
+ path: l,
79
+ type: "unchanged",
80
+ oldValue: a,
81
+ newValue: d
82
+ }) : r.push({
83
+ path: l,
84
+ type: "changed",
85
+ oldValue: a,
86
+ newValue: d
87
+ }) : F(a, d) ? o && r.push({
88
+ path: l,
89
+ type: "unchanged",
90
+ oldValue: a,
91
+ newValue: d
92
+ }) : r.push({
93
+ path: l,
94
+ type: "changed",
95
+ oldValue: a,
96
+ newValue: d
97
+ });
98
+ }
99
+ return r;
100
+ }
101
+ function le(t, e) {
102
+ const n = ve(t, e, "", !1), o = {
103
+ added: 0,
104
+ removed: 0,
105
+ changed: 0,
106
+ unchanged: 0
107
+ };
108
+ for (const r of n)
109
+ o[r.type]++;
110
+ return { changes: n, summary: o };
111
+ }
112
+ function Ft(t) {
113
+ return t.summary.added > 0 || t.summary.removed > 0 || t.summary.changed > 0;
114
+ }
115
+ const Fe = { BASE_URL: "/", DEV: !1, MODE: "production", PROD: !0, SSR: !1 };
116
+ function Oe() {
117
+ try {
118
+ if (typeof import.meta < "u" && Fe)
119
+ return !1;
120
+ const t = globalThis.process;
121
+ if (t?.env) {
122
+ const e = t.env.DEVLOGGER_ENABLED || t.env.REACT_APP_DEVLOGGER_ENABLED;
123
+ if (e === "false" || e === "0") return !1;
124
+ if (e === "true" || e === "1") return !0;
125
+ if (t.env.NODE_ENV === "production") return !1;
126
+ }
127
+ return !0;
128
+ } catch {
129
+ return !0;
130
+ }
131
+ }
132
+ const Ae = {
133
+ maxLogs: 1e3,
134
+ persist: !1,
135
+ minLevel: "debug",
136
+ enabled: Oe()
137
+ };
138
+ function He() {
139
+ const t = "devlogger_session_id";
140
+ try {
141
+ const e = sessionStorage.getItem(t);
142
+ if (e)
143
+ return e;
144
+ const n = `session_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
145
+ return sessionStorage.setItem(t, n), n;
146
+ } catch {
147
+ return `session_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
148
+ }
149
+ }
150
+ function ze() {
151
+ return `log_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
152
+ }
153
+ function me() {
154
+ try {
155
+ const t = new Error().stack;
156
+ if (!t)
157
+ return { file: "unknown", line: 0 };
158
+ const e = t.split(`
159
+ `);
160
+ for (let n = 4; n < e.length; n++) {
161
+ const o = e[n];
162
+ if (!o || o.includes("logger.ts") || o.includes("logger.js"))
163
+ continue;
164
+ const r = o.match(/at\s+(?:(.+?)\s+)?\(?(.+?):(\d+):(\d+)\)?/);
165
+ if (r)
166
+ return {
167
+ function: r[1] || void 0,
168
+ file: ce(r[2] || "unknown"),
169
+ line: parseInt(r[3] || "0", 10),
170
+ column: parseInt(r[4] || "0", 10)
171
+ };
172
+ const s = o.match(/(.+)?@(.+?):(\d+):(\d+)/);
173
+ if (s)
174
+ return {
175
+ function: s[1] || void 0,
176
+ file: ce(s[2] || "unknown"),
177
+ line: parseInt(s[3] || "0", 10),
178
+ column: parseInt(s[4] || "0", 10)
179
+ };
180
+ }
181
+ return { file: "unknown", line: 0 };
182
+ } catch {
183
+ return { file: "unknown", line: 0 };
184
+ }
185
+ }
186
+ function ce(t) {
187
+ let e = t.replace(/^webpack:\/\/[^/]*\//, "").replace(/^\/@fs/, "").replace(/^file:\/\//, "").replace(/\?.*$/, "");
188
+ const n = e.split("/");
189
+ return n.length > 2 && (e = n.slice(-2).join("/")), e;
190
+ }
191
+ function Ne() {
192
+ return `span_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
193
+ }
194
+ class G {
195
+ constructor(e, n, o, r) {
196
+ p(this, "logger");
197
+ p(this, "_event");
198
+ p(this, "_ended", !1);
199
+ this.logger = e, this._event = {
200
+ id: Ne(),
201
+ name: n,
202
+ startTime: Date.now(),
203
+ status: "running",
204
+ parentId: r,
205
+ context: o,
206
+ source: me(),
207
+ sessionId: e.getSessionId()
208
+ }, e.notifySpan(this._event);
209
+ }
210
+ /** Get span ID */
211
+ get id() {
212
+ return this._event.id;
213
+ }
214
+ /** Get span event data */
215
+ get event() {
216
+ return this._event;
217
+ }
218
+ /** Check if span has ended */
219
+ get ended() {
220
+ return this._ended;
221
+ }
222
+ /** Log debug within this span */
223
+ debug(e, ...n) {
224
+ this._ended || this.logger.logWithSpan("debug", e, n, this._event.id, this._event.context);
225
+ }
226
+ /** Log info within this span */
227
+ info(e, ...n) {
228
+ this._ended || this.logger.logWithSpan("info", e, n, this._event.id, this._event.context);
229
+ }
230
+ /** Log warn within this span */
231
+ warn(e, ...n) {
232
+ this._ended || this.logger.logWithSpan("warn", e, n, this._event.id, this._event.context);
233
+ }
234
+ /** Log error within this span */
235
+ error(e, ...n) {
236
+ this._ended || this.logger.logWithSpan("error", e, n, this._event.id, this._event.context);
237
+ }
238
+ /** Create a child span */
239
+ span(e, n) {
240
+ const o = { ...this._event.context, ...n };
241
+ return new G(this.logger, e, o, this._event.id);
242
+ }
243
+ /** End the span successfully */
244
+ end() {
245
+ this.finish("success");
246
+ }
247
+ /** End the span with error status */
248
+ fail(e) {
249
+ e && this.error(typeof e == "string" ? e : e.message, e), this.finish("error");
250
+ }
251
+ /** Internal finish method */
252
+ finish(e) {
253
+ this._ended || (this._ended = !0, this._event.endTime = Date.now(), this._event.duration = this._event.endTime - this._event.startTime, this._event.status = e, this.logger.notifySpan(this._event));
254
+ }
255
+ }
256
+ class ee {
257
+ constructor(e, n) {
258
+ p(this, "logger");
259
+ p(this, "context");
260
+ this.logger = e, this.context = n;
261
+ }
262
+ debug(e, ...n) {
263
+ this.logger.logWithContext("debug", e, n, this.context);
264
+ }
265
+ info(e, ...n) {
266
+ this.logger.logWithContext("info", e, n, this.context);
267
+ }
268
+ warn(e, ...n) {
269
+ this.logger.logWithContext("warn", e, n, this.context);
270
+ }
271
+ error(e, ...n) {
272
+ this.logger.logWithContext("error", e, n, this.context);
273
+ }
274
+ /** Create a span with this context */
275
+ span(e, n) {
276
+ return this.logger.span(e, { ...this.context, ...n });
277
+ }
278
+ /** Create a new context logger with merged context */
279
+ withContext(e) {
280
+ return new ee(this.logger, { ...this.context, ...e });
281
+ }
282
+ }
283
+ class Pe {
284
+ constructor() {
285
+ p(this, "logs", []);
286
+ p(this, "spans", /* @__PURE__ */ new Map());
287
+ p(this, "subscribers", /* @__PURE__ */ new Set());
288
+ p(this, "spanSubscribers", /* @__PURE__ */ new Set());
289
+ p(this, "config", { ...Ae });
290
+ p(this, "sessionId");
291
+ p(this, "globalContext", {});
292
+ this.sessionId = He();
293
+ }
294
+ /**
295
+ * Core logging method - all public methods delegate to this
296
+ */
297
+ log(e, n, o, r, s) {
298
+ try {
299
+ if (!this.config.enabled || se[e] < se[this.config.minLevel])
300
+ return;
301
+ const c = Object.keys(this.globalContext).length > 0 || r ? { ...this.globalContext, ...r } : void 0, l = {
302
+ id: ze(),
303
+ timestamp: Date.now(),
304
+ level: e,
305
+ message: String(n),
306
+ data: this.safeCloneData(o),
307
+ source: me(),
308
+ sessionId: this.sessionId,
309
+ context: c,
310
+ spanId: s
311
+ };
312
+ this.store(l), this.notify(l);
313
+ } catch (c) {
314
+ typeof console < "u" && console.warn && console.warn("[DevLogger] Internal error:", c);
315
+ }
316
+ }
317
+ /**
318
+ * Log with context (used by ContextLogger)
319
+ */
320
+ logWithContext(e, n, o, r) {
321
+ this.log(e, n, o, r);
322
+ }
323
+ /**
324
+ * Log with span (used by LogSpan)
325
+ */
326
+ logWithSpan(e, n, o, r, s) {
327
+ this.log(e, n, o, s, r);
328
+ }
329
+ /**
330
+ * Notify span subscribers
331
+ */
332
+ notifySpan(e) {
333
+ this.spans.set(e.id, e);
334
+ for (const n of this.spanSubscribers)
335
+ try {
336
+ n(e);
337
+ } catch {
338
+ }
339
+ }
340
+ /**
341
+ * Safely clone data to prevent mutations and handle special cases
342
+ */
343
+ safeCloneData(e) {
344
+ return e.map((n) => this.safeClone(n));
345
+ }
346
+ /**
347
+ * Deep clone with circular reference handling
348
+ */
349
+ safeClone(e, n = /* @__PURE__ */ new WeakSet()) {
350
+ if (e == null || typeof e != "object")
351
+ return e;
352
+ if (e instanceof Error)
353
+ return {
354
+ __type: "Error",
355
+ name: e.name,
356
+ message: e.message,
357
+ stack: e.stack
358
+ };
359
+ if (e instanceof Date)
360
+ return { __type: "Date", value: e.toISOString() };
361
+ if (e instanceof RegExp)
362
+ return { __type: "RegExp", value: e.toString() };
363
+ if (n.has(e))
364
+ return "[Circular Reference]";
365
+ if (n.add(e), Array.isArray(e))
366
+ return e.map((o) => this.safeClone(o, n));
367
+ try {
368
+ const o = {};
369
+ for (const r of Object.keys(e))
370
+ o[r] = this.safeClone(e[r], n);
371
+ return o;
372
+ } catch {
373
+ return "[Uncloneable Object]";
374
+ }
375
+ }
376
+ /**
377
+ * Store log with FIFO rotation
378
+ */
379
+ store(e) {
380
+ for (this.logs.push(e); this.logs.length > this.config.maxLogs; )
381
+ this.logs.shift();
382
+ }
383
+ /**
384
+ * Notify all subscribers of new log
385
+ */
386
+ notify(e) {
387
+ for (const n of this.subscribers)
388
+ try {
389
+ n(e);
390
+ } catch {
391
+ }
392
+ }
393
+ // ========== Public API ==========
394
+ /**
395
+ * Log an info message
396
+ */
397
+ info(e, ...n) {
398
+ this.log("info", e, n);
399
+ }
400
+ /**
401
+ * Log a warning message
402
+ */
403
+ warn(e, ...n) {
404
+ this.log("warn", e, n);
405
+ }
406
+ /**
407
+ * Log an error message
408
+ */
409
+ error(e, ...n) {
410
+ this.log("error", e, n);
411
+ }
412
+ /**
413
+ * Log a debug message
414
+ */
415
+ debug(e, ...n) {
416
+ this.log("debug", e, n);
417
+ }
418
+ /**
419
+ * Update logger configuration
420
+ */
421
+ configure(e) {
422
+ try {
423
+ this.config = { ...this.config, ...e };
424
+ } catch {
425
+ }
426
+ }
427
+ /**
428
+ * Clear all stored logs and spans
429
+ */
430
+ clear() {
431
+ try {
432
+ this.logs = [], this.spans.clear();
433
+ } catch {
434
+ }
435
+ }
436
+ /**
437
+ * Import logs (used for rehydration from persistence)
438
+ * Imported logs are added at the beginning, preserving order
439
+ */
440
+ importLogs(e) {
441
+ try {
442
+ if (!Array.isArray(e) || e.length === 0)
443
+ return;
444
+ const n = new Set(this.logs.map((r) => r.id)), o = e.filter((r) => !n.has(r.id));
445
+ for (this.logs = [...o, ...this.logs]; this.logs.length > this.config.maxLogs; )
446
+ this.logs.shift();
447
+ } catch {
448
+ }
449
+ }
450
+ /**
451
+ * Get all stored logs (readonly)
452
+ */
453
+ getLogs() {
454
+ return this.logs;
455
+ }
456
+ /**
457
+ * Subscribe to new log events
458
+ */
459
+ subscribe(e) {
460
+ try {
461
+ return this.subscribers.add(e), () => {
462
+ this.subscribers.delete(e);
463
+ };
464
+ } catch {
465
+ return () => {
466
+ };
467
+ }
468
+ }
469
+ /**
470
+ * Get current session ID
471
+ */
472
+ getSessionId() {
473
+ return this.sessionId;
474
+ }
475
+ /**
476
+ * Get current configuration
477
+ */
478
+ getConfig() {
479
+ return { ...this.config };
480
+ }
481
+ /**
482
+ * Check if logging is currently enabled
483
+ */
484
+ isEnabled() {
485
+ return this.config.enabled;
486
+ }
487
+ // ========== Span API ==========
488
+ /**
489
+ * Create a new span for grouping related logs
490
+ */
491
+ span(e, n) {
492
+ try {
493
+ return new G(this, e, n);
494
+ } catch {
495
+ return new G(this, e, n);
496
+ }
497
+ }
498
+ /**
499
+ * Get all spans
500
+ */
501
+ getSpans() {
502
+ return Array.from(this.spans.values());
503
+ }
504
+ /**
505
+ * Get a specific span by ID
506
+ */
507
+ getSpan(e) {
508
+ return this.spans.get(e);
509
+ }
510
+ /**
511
+ * Get logs belonging to a specific span
512
+ */
513
+ getSpanLogs(e) {
514
+ return this.logs.filter((n) => n.spanId === e);
515
+ }
516
+ /**
517
+ * Subscribe to span events
518
+ */
519
+ subscribeSpans(e) {
520
+ try {
521
+ return this.spanSubscribers.add(e), () => {
522
+ this.spanSubscribers.delete(e);
523
+ };
524
+ } catch {
525
+ return () => {
526
+ };
527
+ }
528
+ }
529
+ // ========== Context API ==========
530
+ /**
531
+ * Set global context that will be attached to all logs
532
+ */
533
+ setGlobalContext(e) {
534
+ try {
535
+ this.globalContext = { ...e };
536
+ } catch {
537
+ }
538
+ }
539
+ /**
540
+ * Update global context (merge with existing)
541
+ */
542
+ updateGlobalContext(e) {
543
+ try {
544
+ this.globalContext = { ...this.globalContext, ...e };
545
+ } catch {
546
+ }
547
+ }
548
+ /**
549
+ * Get current global context
550
+ */
551
+ getGlobalContext() {
552
+ return { ...this.globalContext };
553
+ }
554
+ /**
555
+ * Clear global context
556
+ */
557
+ clearGlobalContext() {
558
+ this.globalContext = {};
559
+ }
560
+ /**
561
+ * Create a context-bound logger
562
+ */
563
+ withContext(e) {
564
+ return new ee(this, e);
565
+ }
566
+ // ========== Export API ==========
567
+ /**
568
+ * Export logs in specified format
569
+ */
570
+ exportLogs(e = {}) {
571
+ try {
572
+ const {
573
+ format: n = "json",
574
+ lastMs: o,
575
+ levels: r,
576
+ search: s,
577
+ pretty: c = !0
578
+ } = e;
579
+ let l = [...this.logs];
580
+ if (o !== void 0 && o > 0) {
581
+ const a = Date.now() - o;
582
+ l = l.filter((d) => d.timestamp >= a);
583
+ }
584
+ if (r && r.length > 0) {
585
+ const a = new Set(r);
586
+ l = l.filter((d) => a.has(d.level));
587
+ }
588
+ if (s) {
589
+ const a = s.toLowerCase();
590
+ l = l.filter(
591
+ (d) => d.message.toLowerCase().includes(a) || JSON.stringify(d.data).toLowerCase().includes(a)
592
+ );
593
+ }
594
+ return n === "text" ? this.formatLogsAsText(l) : c ? JSON.stringify(l, null, 2) : JSON.stringify(l);
595
+ } catch {
596
+ return "[]";
597
+ }
598
+ }
599
+ /**
600
+ * Format logs as human-readable text
601
+ */
602
+ formatLogsAsText(e) {
603
+ return e.map((n) => {
604
+ const o = new Date(n.timestamp).toISOString(), r = n.level.toUpperCase().padEnd(5), s = `${n.source.file}:${n.source.line}`, c = n.context ? ` [${Object.entries(n.context).map(([d, f]) => `${d}=${f}`).join(", ")}]` : "", l = n.spanId ? ` (span: ${n.spanId})` : "", a = n.data.length > 0 ? `
605
+ Data: ${JSON.stringify(n.data)}` : "";
606
+ return `[${o}] ${r} ${n.message}${c}${l}
607
+ Source: ${s}${a}`;
608
+ }).join(`
609
+
610
+ `);
611
+ }
612
+ /**
613
+ * Copy logs to clipboard
614
+ */
615
+ async copyLogs(e = {}) {
616
+ try {
617
+ const n = this.exportLogs(e);
618
+ return await navigator.clipboard.writeText(n), !0;
619
+ } catch {
620
+ return !1;
621
+ }
622
+ }
623
+ // ========== Diff API ==========
624
+ /**
625
+ * Log a visual diff between two objects
626
+ */
627
+ diff(e, n, o, r = "info") {
628
+ try {
629
+ const s = le(n, o);
630
+ return this.log(r, e, [
631
+ {
632
+ __type: "Diff",
633
+ diff: s,
634
+ oldObj: this.safeClone(n),
635
+ newObj: this.safeClone(o)
636
+ }
637
+ ]), s;
638
+ } catch {
639
+ return {
640
+ changes: [],
641
+ summary: { added: 0, removed: 0, changed: 0, unchanged: 0 }
642
+ };
643
+ }
644
+ }
645
+ /**
646
+ * Compute diff without logging (utility method)
647
+ */
648
+ computeDiff(e, n) {
649
+ try {
650
+ return le(e, n);
651
+ } catch {
652
+ return {
653
+ changes: [],
654
+ summary: { added: 0, removed: 0, changed: 0, unchanged: 0 }
655
+ };
656
+ }
657
+ }
658
+ }
659
+ const u = new Pe(), g = {
660
+ // Background
661
+ bgPrimary: "#1e1e1e",
662
+ bgSecondary: "#252526",
663
+ bgHover: "#2a2a2a",
664
+ bgHeader: "#333333",
665
+ // Text
666
+ textPrimary: "#cccccc",
667
+ textSecondary: "#858585",
668
+ textMuted: "#6e6e6e",
669
+ // Log Levels
670
+ levelDebug: "#6e6e6e",
671
+ levelInfo: "#3794ff",
672
+ levelWarn: "#cca700",
673
+ levelError: "#f14c4c",
674
+ // Accents
675
+ border: "#3c3c3c",
676
+ scrollbar: "#4a4a4a",
677
+ scrollbarHover: "#5a5a5a",
678
+ // Interactive
679
+ buttonBg: "#0e639c",
680
+ buttonHover: "#1177bb"
681
+ }, qe = `
682
+ :host {
683
+ --bg-primary: ${g.bgPrimary};
684
+ --bg-secondary: ${g.bgSecondary};
685
+ --bg-hover: ${g.bgHover};
686
+ --bg-header: ${g.bgHeader};
687
+ --text-primary: ${g.textPrimary};
688
+ --text-secondary: ${g.textSecondary};
689
+ --text-muted: ${g.textMuted};
690
+ --border: ${g.border};
691
+
692
+ all: initial;
693
+ font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
694
+ font-size: 12px;
695
+ line-height: 1.4;
696
+ color: var(--text-primary);
697
+ }
698
+
699
+ * {
700
+ box-sizing: border-box;
701
+ }
702
+
703
+ /* Container */
704
+ .devlogger-container {
705
+ position: fixed;
706
+ top: 0;
707
+ right: 0;
708
+ width: 420px;
709
+ height: 100vh;
710
+ background: var(--bg-primary);
711
+ border-left: 1px solid var(--border);
712
+ display: flex;
713
+ flex-direction: column;
714
+ z-index: 99999;
715
+ box-shadow: -2px 0 8px rgba(0, 0, 0, 0.3);
716
+ }
717
+
718
+ .devlogger-container.hidden {
719
+ display: none;
720
+ }
721
+
722
+ /* Header */
723
+ .devlogger-header {
724
+ display: flex;
725
+ align-items: center;
726
+ justify-content: space-between;
727
+ padding: 8px 12px;
728
+ background: var(--bg-header);
729
+ border-bottom: 1px solid var(--border);
730
+ flex-shrink: 0;
731
+ }
732
+
733
+ .devlogger-title {
734
+ font-weight: 600;
735
+ font-size: 13px;
736
+ color: var(--text-primary);
737
+ display: flex;
738
+ align-items: center;
739
+ gap: 8px;
740
+ }
741
+
742
+ .devlogger-badge {
743
+ background: ${g.levelInfo};
744
+ color: white;
745
+ padding: 2px 6px;
746
+ border-radius: 10px;
747
+ font-size: 10px;
748
+ font-weight: 500;
749
+ }
750
+
751
+ .devlogger-actions {
752
+ display: flex;
753
+ gap: 4px;
754
+ }
755
+
756
+ .devlogger-btn {
757
+ background: transparent;
758
+ border: none;
759
+ color: var(--text-secondary);
760
+ cursor: pointer;
761
+ padding: 4px 8px;
762
+ border-radius: 4px;
763
+ font-size: 12px;
764
+ transition: all 0.15s ease;
765
+ }
766
+
767
+ .devlogger-btn:hover {
768
+ background: var(--bg-hover);
769
+ color: var(--text-primary);
770
+ }
771
+
772
+ .devlogger-btn-primary {
773
+ background: ${g.buttonBg};
774
+ color: white;
775
+ }
776
+
777
+ .devlogger-btn-primary:hover {
778
+ background: ${g.buttonHover};
779
+ color: white;
780
+ }
781
+
782
+ /* Log List */
783
+ .devlogger-logs {
784
+ flex: 1;
785
+ overflow-y: auto;
786
+ overflow-x: hidden;
787
+ }
788
+
789
+ .devlogger-logs::-webkit-scrollbar {
790
+ width: 8px;
791
+ }
792
+
793
+ .devlogger-logs::-webkit-scrollbar-track {
794
+ background: var(--bg-primary);
795
+ }
796
+
797
+ .devlogger-logs::-webkit-scrollbar-thumb {
798
+ background: ${g.scrollbar};
799
+ border-radius: 4px;
800
+ }
801
+
802
+ .devlogger-logs::-webkit-scrollbar-thumb:hover {
803
+ background: ${g.scrollbarHover};
804
+ }
805
+
806
+ .devlogger-empty {
807
+ display: flex;
808
+ align-items: center;
809
+ justify-content: center;
810
+ height: 100%;
811
+ color: var(--text-muted);
812
+ font-style: italic;
813
+ }
814
+
815
+ /* Log Entry */
816
+ .log-entry {
817
+ border-bottom: 1px solid var(--border);
818
+ padding: 8px 12px;
819
+ transition: background 0.1s ease;
820
+ }
821
+
822
+ .log-entry:hover {
823
+ background: var(--bg-hover);
824
+ }
825
+
826
+ .log-entry-header {
827
+ display: flex;
828
+ align-items: center;
829
+ gap: 8px;
830
+ margin-bottom: 4px;
831
+ }
832
+
833
+ .log-level {
834
+ font-weight: 600;
835
+ font-size: 10px;
836
+ text-transform: uppercase;
837
+ padding: 2px 6px;
838
+ border-radius: 3px;
839
+ min-width: 45px;
840
+ text-align: center;
841
+ }
842
+
843
+ .log-level-debug {
844
+ background: rgba(110, 110, 110, 0.2);
845
+ color: ${g.levelDebug};
846
+ }
847
+
848
+ .log-level-info {
849
+ background: rgba(55, 148, 255, 0.15);
850
+ color: ${g.levelInfo};
851
+ }
852
+
853
+ .log-level-warn {
854
+ background: rgba(204, 167, 0, 0.15);
855
+ color: ${g.levelWarn};
856
+ }
857
+
858
+ .log-level-error {
859
+ background: rgba(241, 76, 76, 0.15);
860
+ color: ${g.levelError};
861
+ }
862
+
863
+ .log-time {
864
+ color: var(--text-muted);
865
+ font-size: 11px;
866
+ }
867
+
868
+ .log-source {
869
+ color: var(--text-secondary);
870
+ font-size: 11px;
871
+ margin-left: auto;
872
+ max-width: 150px;
873
+ overflow: hidden;
874
+ text-overflow: ellipsis;
875
+ white-space: nowrap;
876
+ }
877
+
878
+ .log-source:hover {
879
+ color: ${g.levelInfo};
880
+ }
881
+
882
+ .log-context {
883
+ font-size: 10px;
884
+ color: ${g.levelInfo};
885
+ background: rgba(55, 148, 255, 0.1);
886
+ padding: 2px 6px;
887
+ border-radius: 3px;
888
+ max-width: 150px;
889
+ overflow: hidden;
890
+ text-overflow: ellipsis;
891
+ white-space: nowrap;
892
+ }
893
+
894
+ .log-entry-in-span {
895
+ border-left: 2px solid ${g.levelInfo};
896
+ padding-left: 10px;
897
+ }
898
+
899
+ .log-message {
900
+ color: var(--text-primary);
901
+ word-break: break-word;
902
+ margin-bottom: 4px;
903
+ }
904
+
905
+ /* Data Display */
906
+ .log-data {
907
+ margin-top: 6px;
908
+ }
909
+
910
+ .log-data-toggle {
911
+ background: transparent;
912
+ border: none;
913
+ color: var(--text-secondary);
914
+ cursor: pointer;
915
+ padding: 2px 0;
916
+ font-size: 11px;
917
+ display: flex;
918
+ align-items: center;
919
+ gap: 4px;
920
+ }
921
+
922
+ .log-data-toggle:hover {
923
+ color: var(--text-primary);
924
+ }
925
+
926
+ .log-data-toggle::before {
927
+ content: '▶';
928
+ font-size: 8px;
929
+ transition: transform 0.15s ease;
930
+ }
931
+
932
+ .log-data-toggle.expanded::before {
933
+ transform: rotate(90deg);
934
+ }
935
+
936
+ .log-data-content {
937
+ display: none;
938
+ margin-top: 6px;
939
+ padding: 8px;
940
+ background: var(--bg-secondary);
941
+ border-radius: 4px;
942
+ font-size: 11px;
943
+ overflow-x: auto;
944
+ white-space: pre-wrap;
945
+ word-break: break-all;
946
+ }
947
+
948
+ .log-data-content.visible {
949
+ display: block;
950
+ }
951
+
952
+ /* Toggle Button (floating) */
953
+ .devlogger-toggle {
954
+ position: fixed;
955
+ bottom: 20px;
956
+ right: 20px;
957
+ width: 48px;
958
+ height: 48px;
959
+ border-radius: 50%;
960
+ background: ${g.buttonBg};
961
+ color: white;
962
+ border: none;
963
+ cursor: pointer;
964
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
965
+ font-size: 20px;
966
+ display: flex;
967
+ align-items: center;
968
+ justify-content: center;
969
+ transition: all 0.2s ease;
970
+ z-index: 99998;
971
+ }
972
+
973
+ .devlogger-toggle:hover {
974
+ background: ${g.buttonHover};
975
+ transform: scale(1.05);
976
+ }
977
+
978
+ .devlogger-toggle.hidden {
979
+ display: none;
980
+ }
981
+
982
+ /* Footer / Status */
983
+ .devlogger-footer {
984
+ padding: 6px 12px;
985
+ background: var(--bg-header);
986
+ border-top: 1px solid var(--border);
987
+ font-size: 11px;
988
+ color: var(--text-muted);
989
+ display: flex;
990
+ justify-content: space-between;
991
+ flex-shrink: 0;
992
+ }
993
+
994
+ .devlogger-shortcut {
995
+ opacity: 0.7;
996
+ }
997
+
998
+ /* Filter Bar */
999
+ .filter-bar {
1000
+ padding: 8px 12px;
1001
+ background: var(--bg-secondary);
1002
+ border-bottom: 1px solid var(--border);
1003
+ flex-shrink: 0;
1004
+ }
1005
+
1006
+ .filter-bar.filter-active {
1007
+ background: rgba(55, 148, 255, 0.08);
1008
+ border-bottom-color: ${g.levelInfo};
1009
+ }
1010
+
1011
+ .filter-row {
1012
+ display: flex;
1013
+ align-items: center;
1014
+ gap: 8px;
1015
+ }
1016
+
1017
+ .filter-levels {
1018
+ display: flex;
1019
+ gap: 2px;
1020
+ }
1021
+
1022
+ .filter-level-btn {
1023
+ width: 24px;
1024
+ height: 24px;
1025
+ border: 1px solid var(--border);
1026
+ border-radius: 4px;
1027
+ background: transparent;
1028
+ color: var(--text-muted);
1029
+ cursor: pointer;
1030
+ font-size: 10px;
1031
+ font-weight: 600;
1032
+ transition: all 0.15s ease;
1033
+ }
1034
+
1035
+ .filter-level-btn:hover {
1036
+ background: var(--bg-hover);
1037
+ color: var(--text-primary);
1038
+ }
1039
+
1040
+ .filter-level-btn.active {
1041
+ color: white;
1042
+ }
1043
+
1044
+ .filter-level-btn.active[data-level="debug"] {
1045
+ background: ${g.levelDebug};
1046
+ border-color: ${g.levelDebug};
1047
+ }
1048
+
1049
+ .filter-level-btn.active[data-level="info"] {
1050
+ background: ${g.levelInfo};
1051
+ border-color: ${g.levelInfo};
1052
+ }
1053
+
1054
+ .filter-level-btn.active[data-level="warn"] {
1055
+ background: ${g.levelWarn};
1056
+ border-color: ${g.levelWarn};
1057
+ }
1058
+
1059
+ .filter-level-btn.active[data-level="error"] {
1060
+ background: ${g.levelError};
1061
+ border-color: ${g.levelError};
1062
+ }
1063
+
1064
+ .filter-search {
1065
+ flex: 1;
1066
+ }
1067
+
1068
+ .filter-file {
1069
+ width: 120px;
1070
+ }
1071
+
1072
+ .filter-input {
1073
+ width: 100%;
1074
+ padding: 4px 8px;
1075
+ border: 1px solid var(--border);
1076
+ border-radius: 4px;
1077
+ background: var(--bg-primary);
1078
+ color: var(--text-primary);
1079
+ font-size: 11px;
1080
+ font-family: inherit;
1081
+ outline: none;
1082
+ transition: border-color 0.15s ease;
1083
+ }
1084
+
1085
+ .filter-input::placeholder {
1086
+ color: var(--text-muted);
1087
+ }
1088
+
1089
+ .filter-input:focus {
1090
+ border-color: ${g.levelInfo};
1091
+ }
1092
+
1093
+ .filter-clear-btn {
1094
+ width: 24px;
1095
+ height: 24px;
1096
+ border: none;
1097
+ border-radius: 4px;
1098
+ background: rgba(241, 76, 76, 0.2);
1099
+ color: ${g.levelError};
1100
+ cursor: pointer;
1101
+ font-size: 12px;
1102
+ transition: all 0.15s ease;
1103
+ }
1104
+
1105
+ .filter-clear-btn:hover {
1106
+ background: ${g.levelError};
1107
+ color: white;
1108
+ }
1109
+
1110
+ .filter-status {
1111
+ margin-top: 6px;
1112
+ font-size: 10px;
1113
+ color: ${g.levelInfo};
1114
+ }
1115
+
1116
+ /* No results state */
1117
+ .devlogger-no-results {
1118
+ display: flex;
1119
+ flex-direction: column;
1120
+ align-items: center;
1121
+ justify-content: center;
1122
+ height: 100%;
1123
+ color: var(--text-muted);
1124
+ gap: 8px;
1125
+ }
1126
+
1127
+ .devlogger-no-results-icon {
1128
+ font-size: 24px;
1129
+ opacity: 0.5;
1130
+ }
1131
+
1132
+ .devlogger-no-results-text {
1133
+ font-style: italic;
1134
+ }
1135
+
1136
+ /* Diff Display */
1137
+ .diff-container {
1138
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
1139
+ font-size: 11px;
1140
+ }
1141
+
1142
+ .diff-summary {
1143
+ display: flex;
1144
+ gap: 8px;
1145
+ margin-bottom: 8px;
1146
+ padding-bottom: 8px;
1147
+ border-bottom: 1px solid var(--border);
1148
+ }
1149
+
1150
+ .diff-count {
1151
+ padding: 2px 6px;
1152
+ border-radius: 3px;
1153
+ font-weight: 600;
1154
+ }
1155
+
1156
+ .diff-count.diff-added {
1157
+ background: rgba(80, 200, 120, 0.2);
1158
+ color: #50c878;
1159
+ }
1160
+
1161
+ .diff-count.diff-removed {
1162
+ background: rgba(241, 76, 76, 0.2);
1163
+ color: ${g.levelError};
1164
+ }
1165
+
1166
+ .diff-count.diff-changed {
1167
+ background: rgba(204, 167, 0, 0.2);
1168
+ color: ${g.levelWarn};
1169
+ }
1170
+
1171
+ .diff-count.diff-unchanged {
1172
+ background: var(--bg-hover);
1173
+ color: var(--text-muted);
1174
+ }
1175
+
1176
+ .diff-changes {
1177
+ display: flex;
1178
+ flex-direction: column;
1179
+ gap: 2px;
1180
+ }
1181
+
1182
+ .diff-entry {
1183
+ padding: 2px 6px;
1184
+ border-radius: 3px;
1185
+ display: flex;
1186
+ gap: 6px;
1187
+ }
1188
+
1189
+ .diff-entry.diff-added {
1190
+ background: rgba(80, 200, 120, 0.1);
1191
+ color: #50c878;
1192
+ }
1193
+
1194
+ .diff-entry.diff-removed {
1195
+ background: rgba(241, 76, 76, 0.1);
1196
+ color: ${g.levelError};
1197
+ }
1198
+
1199
+ .diff-entry.diff-changed {
1200
+ background: rgba(204, 167, 0, 0.1);
1201
+ color: ${g.levelWarn};
1202
+ }
1203
+
1204
+ .diff-icon {
1205
+ font-weight: bold;
1206
+ width: 12px;
1207
+ flex-shrink: 0;
1208
+ }
1209
+
1210
+ .diff-path {
1211
+ color: var(--text-secondary);
1212
+ flex-shrink: 0;
1213
+ }
1214
+
1215
+ .diff-value {
1216
+ word-break: break-all;
1217
+ }
1218
+
1219
+ .log-data-diff .log-data-toggle {
1220
+ color: ${g.levelInfo};
1221
+ }
1222
+ `;
1223
+ function Ue(t) {
1224
+ const e = new Date(t), n = e.getHours().toString().padStart(2, "0"), o = e.getMinutes().toString().padStart(2, "0"), r = e.getSeconds().toString().padStart(2, "0"), s = e.getMilliseconds().toString().padStart(3, "0");
1225
+ return `${n}:${o}:${r}.${s}`;
1226
+ }
1227
+ function de(t) {
1228
+ return t.file === "unknown" ? "unknown" : `${t.file}:${t.line}`;
1229
+ }
1230
+ function ye(t) {
1231
+ return t !== null && typeof t == "object" && t.__type === "Diff" && t.diff !== void 0;
1232
+ }
1233
+ function $(t) {
1234
+ if (t === void 0) return "undefined";
1235
+ if (t === null) return "null";
1236
+ if (typeof t == "string") return `"${t}"`;
1237
+ if (typeof t == "number" || typeof t == "boolean") return String(t);
1238
+ if (Array.isArray(t))
1239
+ return t.length === 0 ? "[]" : t.length <= 3 ? `[${t.map($).join(", ")}]` : `[${t.length} items]`;
1240
+ if (typeof t == "object") {
1241
+ const e = Object.keys(t);
1242
+ return e.length === 0 ? "{}" : e.length <= 2 ? `{${e.map((n) => `${n}: ${$(t[n])}`).join(", ")}}` : `{${e.length} keys}`;
1243
+ }
1244
+ return String(t);
1245
+ }
1246
+ function Ve(t) {
1247
+ const e = `diff-${t.type}`, n = t.type === "added" ? "+" : t.type === "removed" ? "-" : t.type === "changed" ? "~" : " ";
1248
+ let o = "";
1249
+ return t.type === "added" ? o = $(t.newValue) : t.type === "removed" ? o = $(t.oldValue) : t.type === "changed" && (o = `${$(t.oldValue)} → ${$(t.newValue)}`), `<div class="diff-entry ${e}"><span class="diff-icon">${n}</span> <span class="diff-path">${t.path}</span>: <span class="diff-value">${o}</span></div>`;
1250
+ }
1251
+ function je(t) {
1252
+ const { diff: e } = t, { summary: n, changes: o } = e;
1253
+ let r = '<div class="diff-container">';
1254
+ if (r += '<div class="diff-summary">', n.added > 0 && (r += `<span class="diff-count diff-added">+${n.added}</span>`), n.removed > 0 && (r += `<span class="diff-count diff-removed">-${n.removed}</span>`), n.changed > 0 && (r += `<span class="diff-count diff-changed">~${n.changed}</span>`), n.added === 0 && n.removed === 0 && n.changed === 0 && (r += '<span class="diff-count diff-unchanged">No changes</span>'), r += "</div>", o.length > 0) {
1255
+ r += '<div class="diff-changes">';
1256
+ for (const s of o)
1257
+ r += Ve(s);
1258
+ r += "</div>";
1259
+ }
1260
+ return r += "</div>", r;
1261
+ }
1262
+ function We(t) {
1263
+ if (t.length === 0)
1264
+ return "";
1265
+ try {
1266
+ return t.map((e) => typeof e == "string" ? e : JSON.stringify(e, null, 2)).join(`
1267
+ `);
1268
+ } catch {
1269
+ return "[Unable to display data]";
1270
+ }
1271
+ }
1272
+ function Ge(t) {
1273
+ return t.some(ye);
1274
+ }
1275
+ function M(t) {
1276
+ const e = document.createElement("div");
1277
+ return e.textContent = t, e.innerHTML;
1278
+ }
1279
+ function Je(t) {
1280
+ return !t || Object.keys(t).length === 0 ? "" : Object.entries(t).map(([e, n]) => `${e}=${n}`).join(" · ");
1281
+ }
1282
+ function xe(t) {
1283
+ const e = document.createElement("div");
1284
+ e.className = `log-entry${t.spanId ? " log-entry-in-span" : ""}`, e.dataset.id = t.id, t.spanId && (e.dataset.spanId = t.spanId);
1285
+ const n = t.data.length > 0, o = Ge(t.data), r = t.context && Object.keys(t.context).length > 0, s = `data-${t.id}`, c = () => {
1286
+ if (!n) return "";
1287
+ if (o) {
1288
+ const l = t.data.find(ye);
1289
+ if (l)
1290
+ return `
1291
+ <div class="log-data log-data-diff">
1292
+ <button class="log-data-toggle" data-target="${s}">
1293
+ diff
1294
+ </button>
1295
+ <div class="log-data-content" id="${s}">${je(l)}</div>
1296
+ </div>
1297
+ `;
1298
+ }
1299
+ return `
1300
+ <div class="log-data">
1301
+ <button class="log-data-toggle" data-target="${s}">
1302
+ ${t.data.length} item${t.data.length > 1 ? "s" : ""}
1303
+ </button>
1304
+ <pre class="log-data-content" id="${s}">${M(We(t.data))}</pre>
1305
+ </div>
1306
+ `;
1307
+ };
1308
+ if (e.innerHTML = `
1309
+ <div class="log-entry-header">
1310
+ <span class="log-level log-level-${t.level}">${t.level}</span>
1311
+ <span class="log-time">${Ue(t.timestamp)}</span>
1312
+ ${r ? `<span class="log-context" title="Context">${M(Je(t.context))}</span>` : ""}
1313
+ <span class="log-source" title="${M(de(t.source))}">${M(de(t.source))}</span>
1314
+ </div>
1315
+ <div class="log-message">${M(t.message)}</div>
1316
+ ${c()}
1317
+ `, n) {
1318
+ const l = e.querySelector(".log-data-toggle"), a = e.querySelector(`#${s}`);
1319
+ l && a && l.addEventListener("click", () => {
1320
+ l.classList.toggle("expanded"), a.classList.toggle("visible");
1321
+ });
1322
+ }
1323
+ return e;
1324
+ }
1325
+ function Xe() {
1326
+ const t = document.createElement("div");
1327
+ return t.className = "devlogger-empty", t.textContent = "No logs yet...", t;
1328
+ }
1329
+ const Ye = "devlogger-sync";
1330
+ function Ke() {
1331
+ return `sender_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
1332
+ }
1333
+ class Qe {
1334
+ constructor() {
1335
+ p(this, "channel", null);
1336
+ p(this, "handlers", /* @__PURE__ */ new Set());
1337
+ p(this, "senderId");
1338
+ p(this, "isConnected", !1);
1339
+ this.senderId = Ke(), this.connect();
1340
+ }
1341
+ /**
1342
+ * Connect to the broadcast channel
1343
+ */
1344
+ connect() {
1345
+ try {
1346
+ if (typeof BroadcastChannel > "u") {
1347
+ console.warn("[DevLogger] BroadcastChannel not supported");
1348
+ return;
1349
+ }
1350
+ this.channel = new BroadcastChannel(Ye), this.channel.onmessage = (e) => {
1351
+ this.handleMessage(e.data);
1352
+ }, this.channel.onmessageerror = () => {
1353
+ console.warn("[DevLogger] Channel message error");
1354
+ }, this.isConnected = !0;
1355
+ } catch (e) {
1356
+ console.warn("[DevLogger] Failed to connect to channel:", e), this.isConnected = !1;
1357
+ }
1358
+ }
1359
+ /**
1360
+ * Handle incoming messages
1361
+ */
1362
+ handleMessage(e) {
1363
+ if (e.senderId !== this.senderId)
1364
+ for (const n of this.handlers)
1365
+ try {
1366
+ n(e);
1367
+ } catch {
1368
+ }
1369
+ }
1370
+ /**
1371
+ * Send a message to all connected windows/tabs
1372
+ */
1373
+ send(e, n) {
1374
+ try {
1375
+ if (!this.channel || !this.isConnected)
1376
+ return;
1377
+ const o = {
1378
+ type: e,
1379
+ payload: n,
1380
+ senderId: this.senderId,
1381
+ timestamp: Date.now()
1382
+ };
1383
+ this.channel.postMessage(o);
1384
+ } catch {
1385
+ }
1386
+ }
1387
+ /**
1388
+ * Send a new log to all connected windows
1389
+ */
1390
+ sendLog(e) {
1391
+ this.send("NEW_LOG", e);
1392
+ }
1393
+ /**
1394
+ * Request all logs from main window
1395
+ */
1396
+ requestSync() {
1397
+ this.send("SYNC_REQUEST");
1398
+ }
1399
+ /**
1400
+ * Send all logs as sync response
1401
+ */
1402
+ sendSyncResponse(e) {
1403
+ this.send("SYNC_RESPONSE", e);
1404
+ }
1405
+ /**
1406
+ * Notify all windows to clear logs
1407
+ */
1408
+ sendClear() {
1409
+ this.send("CLEAR_LOGS");
1410
+ }
1411
+ /**
1412
+ * Subscribe to channel messages
1413
+ */
1414
+ subscribe(e) {
1415
+ return this.handlers.add(e), () => {
1416
+ this.handlers.delete(e);
1417
+ };
1418
+ }
1419
+ /**
1420
+ * Check if channel is connected
1421
+ */
1422
+ isActive() {
1423
+ return this.isConnected;
1424
+ }
1425
+ /**
1426
+ * Get this window's sender ID
1427
+ */
1428
+ getSenderId() {
1429
+ return this.senderId;
1430
+ }
1431
+ /**
1432
+ * Close the channel
1433
+ */
1434
+ close() {
1435
+ try {
1436
+ this.channel && (this.channel.close(), this.channel = null), this.handlers.clear(), this.isConnected = !1;
1437
+ } catch {
1438
+ }
1439
+ }
1440
+ }
1441
+ const H = new Qe(), ge = 500, fe = 700;
1442
+ let b = null;
1443
+ function Ze() {
1444
+ try {
1445
+ if (b && !b.closed)
1446
+ return b.focus(), b;
1447
+ const t = Math.max(0, (screen.width - ge) / 2), e = Math.max(0, (screen.height - fe) / 2), n = tt();
1448
+ return b = window.open(
1449
+ "",
1450
+ "devlogger-popout",
1451
+ `width=${ge},height=${fe},left=${t},top=${e},resizable=yes,scrollbars=yes`
1452
+ ), b ? (b.document.open(), b.document.write(n), b.document.close(), b.addEventListener("load", () => {
1453
+ setTimeout(() => {
1454
+ H.sendSyncResponse(u.getLogs());
1455
+ }, 100);
1456
+ }), b) : (console.warn("[DevLogger] Pop-out blocked by browser"), null);
1457
+ } catch (t) {
1458
+ return console.warn("[DevLogger] Failed to open pop-out:", t), null;
1459
+ }
1460
+ }
1461
+ function ue() {
1462
+ try {
1463
+ b && !b.closed && b.close(), b = null;
1464
+ } catch {
1465
+ }
1466
+ }
1467
+ function et() {
1468
+ return b !== null && !b.closed;
1469
+ }
1470
+ function tt() {
1471
+ return `<!DOCTYPE html>
1472
+ <html lang="en">
1473
+ <head>
1474
+ <meta charset="UTF-8">
1475
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1476
+ <title>DevLogger - Pop-out</title>
1477
+ <style>
1478
+ :root {
1479
+ --bg-primary: #1e1e1e;
1480
+ --bg-secondary: #252526;
1481
+ --bg-hover: #2a2a2a;
1482
+ --bg-header: #333333;
1483
+ --text-primary: #cccccc;
1484
+ --text-secondary: #858585;
1485
+ --text-muted: #6e6e6e;
1486
+ --border: #3c3c3c;
1487
+ --level-debug: #6e6e6e;
1488
+ --level-info: #3794ff;
1489
+ --level-warn: #cca700;
1490
+ --level-error: #f14c4c;
1491
+ --button-bg: #0e639c;
1492
+ --button-hover: #1177bb;
1493
+ }
1494
+
1495
+ * {
1496
+ box-sizing: border-box;
1497
+ margin: 0;
1498
+ padding: 0;
1499
+ }
1500
+
1501
+ body {
1502
+ font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
1503
+ font-size: 12px;
1504
+ line-height: 1.4;
1505
+ background: var(--bg-primary);
1506
+ color: var(--text-primary);
1507
+ height: 100vh;
1508
+ display: flex;
1509
+ flex-direction: column;
1510
+ }
1511
+
1512
+ .header {
1513
+ display: flex;
1514
+ align-items: center;
1515
+ justify-content: space-between;
1516
+ padding: 12px 16px;
1517
+ background: var(--bg-header);
1518
+ border-bottom: 1px solid var(--border);
1519
+ }
1520
+
1521
+ .title {
1522
+ font-weight: 600;
1523
+ font-size: 14px;
1524
+ display: flex;
1525
+ align-items: center;
1526
+ gap: 8px;
1527
+ }
1528
+
1529
+ .badge {
1530
+ background: var(--level-info);
1531
+ color: white;
1532
+ padding: 2px 8px;
1533
+ border-radius: 10px;
1534
+ font-size: 11px;
1535
+ }
1536
+
1537
+ .status {
1538
+ display: flex;
1539
+ align-items: center;
1540
+ gap: 6px;
1541
+ font-size: 11px;
1542
+ color: var(--text-muted);
1543
+ }
1544
+
1545
+ .status-dot {
1546
+ width: 8px;
1547
+ height: 8px;
1548
+ border-radius: 50%;
1549
+ background: #4caf50;
1550
+ }
1551
+
1552
+ .status-dot.disconnected {
1553
+ background: var(--level-error);
1554
+ }
1555
+
1556
+ .actions {
1557
+ display: flex;
1558
+ gap: 8px;
1559
+ }
1560
+
1561
+ .btn {
1562
+ background: transparent;
1563
+ border: 1px solid var(--border);
1564
+ color: var(--text-secondary);
1565
+ padding: 6px 12px;
1566
+ border-radius: 4px;
1567
+ cursor: pointer;
1568
+ font-size: 12px;
1569
+ transition: all 0.15s ease;
1570
+ }
1571
+
1572
+ .btn:hover {
1573
+ background: var(--bg-hover);
1574
+ color: var(--text-primary);
1575
+ }
1576
+
1577
+ .btn-copy {
1578
+ font-size: 10px;
1579
+ padding: 4px 8px;
1580
+ }
1581
+
1582
+ .btn-copy.success {
1583
+ background: #4caf50;
1584
+ border-color: #4caf50;
1585
+ color: white;
1586
+ }
1587
+
1588
+ .btn-copy.error {
1589
+ background: var(--level-error);
1590
+ border-color: var(--level-error);
1591
+ color: white;
1592
+ }
1593
+
1594
+ .btn-timeline {
1595
+ font-size: 10px;
1596
+ padding: 4px 8px;
1597
+ }
1598
+
1599
+ .btn-timeline.active {
1600
+ background: var(--level-info);
1601
+ border-color: var(--level-info);
1602
+ color: white;
1603
+ }
1604
+
1605
+ /* Timeline Container */
1606
+ .timeline-container {
1607
+ position: relative;
1608
+ background: var(--bg-secondary);
1609
+ border-bottom: 1px solid var(--border);
1610
+ padding: 8px;
1611
+ }
1612
+
1613
+ #timeline-canvas {
1614
+ width: 100%;
1615
+ height: 120px;
1616
+ background: var(--bg-primary);
1617
+ border-radius: 4px;
1618
+ }
1619
+
1620
+ .timeline-tooltip {
1621
+ position: absolute;
1622
+ background: var(--bg-header);
1623
+ border: 1px solid var(--border);
1624
+ border-radius: 4px;
1625
+ padding: 6px 10px;
1626
+ font-size: 11px;
1627
+ pointer-events: none;
1628
+ display: none;
1629
+ z-index: 100;
1630
+ max-width: 250px;
1631
+ }
1632
+
1633
+ .timeline-controls {
1634
+ display: flex;
1635
+ justify-content: center;
1636
+ gap: 4px;
1637
+ margin-top: 6px;
1638
+ }
1639
+
1640
+ .timeline-btn {
1641
+ background: transparent;
1642
+ border: 1px solid var(--border);
1643
+ color: var(--text-muted);
1644
+ padding: 2px 8px;
1645
+ border-radius: 3px;
1646
+ cursor: pointer;
1647
+ font-size: 10px;
1648
+ }
1649
+
1650
+ .timeline-btn:hover {
1651
+ background: var(--bg-hover);
1652
+ color: var(--text-primary);
1653
+ }
1654
+
1655
+ .timeline-btn.active {
1656
+ background: var(--button-bg);
1657
+ border-color: var(--button-bg);
1658
+ color: white;
1659
+ }
1660
+
1661
+ /* Filter Bar Styles */
1662
+ .filter-bar {
1663
+ padding: 8px 16px;
1664
+ background: var(--bg-secondary);
1665
+ border-bottom: 1px solid var(--border);
1666
+ }
1667
+
1668
+ .filter-bar.filter-active {
1669
+ border-bottom-color: var(--level-info);
1670
+ }
1671
+
1672
+ .filter-row {
1673
+ display: flex;
1674
+ align-items: center;
1675
+ gap: 8px;
1676
+ }
1677
+
1678
+ .filter-levels {
1679
+ display: flex;
1680
+ gap: 4px;
1681
+ }
1682
+
1683
+ .filter-level-btn {
1684
+ width: 24px;
1685
+ height: 24px;
1686
+ border: 1px solid var(--border);
1687
+ border-radius: 4px;
1688
+ background: transparent;
1689
+ color: var(--text-muted);
1690
+ cursor: pointer;
1691
+ font-size: 11px;
1692
+ font-weight: 600;
1693
+ transition: all 0.15s ease;
1694
+ }
1695
+
1696
+ .filter-level-btn:hover {
1697
+ background: var(--bg-hover);
1698
+ color: var(--text-primary);
1699
+ }
1700
+
1701
+ .filter-level-btn.active {
1702
+ background: var(--bg-hover);
1703
+ color: var(--text-primary);
1704
+ border-color: var(--text-secondary);
1705
+ }
1706
+
1707
+ .filter-level-btn[data-level="debug"].active { border-color: var(--level-debug); color: var(--level-debug); }
1708
+ .filter-level-btn[data-level="info"].active { border-color: var(--level-info); color: var(--level-info); }
1709
+ .filter-level-btn[data-level="warn"].active { border-color: var(--level-warn); color: var(--level-warn); }
1710
+ .filter-level-btn[data-level="error"].active { border-color: var(--level-error); color: var(--level-error); }
1711
+
1712
+ .filter-input {
1713
+ flex: 1;
1714
+ min-width: 80px;
1715
+ padding: 4px 8px;
1716
+ border: 1px solid var(--border);
1717
+ border-radius: 4px;
1718
+ background: var(--bg-primary);
1719
+ color: var(--text-primary);
1720
+ font-size: 12px;
1721
+ font-family: inherit;
1722
+ }
1723
+
1724
+ .filter-input:focus {
1725
+ outline: none;
1726
+ border-color: var(--level-info);
1727
+ }
1728
+
1729
+ .filter-input::placeholder {
1730
+ color: var(--text-muted);
1731
+ }
1732
+
1733
+ .filter-clear-btn {
1734
+ width: 24px;
1735
+ height: 24px;
1736
+ border: 1px solid var(--border);
1737
+ border-radius: 4px;
1738
+ background: transparent;
1739
+ color: var(--text-muted);
1740
+ cursor: pointer;
1741
+ font-size: 14px;
1742
+ line-height: 1;
1743
+ transition: all 0.15s ease;
1744
+ }
1745
+
1746
+ .filter-clear-btn:hover {
1747
+ background: var(--level-error);
1748
+ border-color: var(--level-error);
1749
+ color: white;
1750
+ }
1751
+
1752
+ .filter-status {
1753
+ font-size: 11px;
1754
+ color: var(--text-muted);
1755
+ margin-top: 6px;
1756
+ }
1757
+
1758
+ .logs {
1759
+ flex: 1;
1760
+ overflow-y: auto;
1761
+ overflow-x: hidden;
1762
+ }
1763
+
1764
+ .logs::-webkit-scrollbar {
1765
+ width: 8px;
1766
+ }
1767
+
1768
+ .logs::-webkit-scrollbar-track {
1769
+ background: var(--bg-primary);
1770
+ }
1771
+
1772
+ .logs::-webkit-scrollbar-thumb {
1773
+ background: #4a4a4a;
1774
+ border-radius: 4px;
1775
+ }
1776
+
1777
+ .empty {
1778
+ display: flex;
1779
+ align-items: center;
1780
+ justify-content: center;
1781
+ height: 100%;
1782
+ color: var(--text-muted);
1783
+ font-style: italic;
1784
+ }
1785
+
1786
+ .log-entry {
1787
+ border-bottom: 1px solid var(--border);
1788
+ padding: 10px 16px;
1789
+ }
1790
+
1791
+ .log-entry:hover {
1792
+ background: var(--bg-hover);
1793
+ }
1794
+
1795
+ .log-header {
1796
+ display: flex;
1797
+ align-items: center;
1798
+ gap: 10px;
1799
+ margin-bottom: 4px;
1800
+ }
1801
+
1802
+ .log-level {
1803
+ font-weight: 600;
1804
+ font-size: 10px;
1805
+ text-transform: uppercase;
1806
+ padding: 2px 6px;
1807
+ border-radius: 3px;
1808
+ min-width: 45px;
1809
+ text-align: center;
1810
+ }
1811
+
1812
+ .log-level-debug { background: rgba(110, 110, 110, 0.2); color: var(--level-debug); }
1813
+ .log-level-info { background: rgba(55, 148, 255, 0.15); color: var(--level-info); }
1814
+ .log-level-warn { background: rgba(204, 167, 0, 0.15); color: var(--level-warn); }
1815
+ .log-level-error { background: rgba(241, 76, 76, 0.15); color: var(--level-error); }
1816
+
1817
+ .log-time {
1818
+ color: var(--text-muted);
1819
+ font-size: 11px;
1820
+ }
1821
+
1822
+ .log-source {
1823
+ color: var(--text-secondary);
1824
+ font-size: 11px;
1825
+ margin-left: auto;
1826
+ }
1827
+
1828
+ .log-message {
1829
+ color: var(--text-primary);
1830
+ word-break: break-word;
1831
+ }
1832
+
1833
+ .log-data {
1834
+ margin-top: 6px;
1835
+ }
1836
+
1837
+ .log-data-toggle {
1838
+ background: transparent;
1839
+ border: none;
1840
+ color: var(--text-secondary);
1841
+ cursor: pointer;
1842
+ padding: 2px 0;
1843
+ font-size: 11px;
1844
+ display: flex;
1845
+ align-items: center;
1846
+ gap: 4px;
1847
+ }
1848
+
1849
+ .log-data-toggle:hover {
1850
+ color: var(--text-primary);
1851
+ }
1852
+
1853
+ .log-data-toggle::before {
1854
+ content: '▶';
1855
+ font-size: 8px;
1856
+ transition: transform 0.15s ease;
1857
+ }
1858
+
1859
+ .log-data-toggle.expanded::before {
1860
+ transform: rotate(90deg);
1861
+ }
1862
+
1863
+ .log-data-content {
1864
+ display: none;
1865
+ margin-top: 6px;
1866
+ padding: 8px;
1867
+ background: var(--bg-secondary);
1868
+ border-radius: 4px;
1869
+ font-size: 11px;
1870
+ overflow-x: auto;
1871
+ white-space: pre-wrap;
1872
+ word-break: break-all;
1873
+ }
1874
+
1875
+ .log-data-content.visible {
1876
+ display: block;
1877
+ }
1878
+
1879
+ .footer {
1880
+ padding: 8px 16px;
1881
+ background: var(--bg-header);
1882
+ border-top: 1px solid var(--border);
1883
+ font-size: 11px;
1884
+ color: var(--text-muted);
1885
+ display: flex;
1886
+ justify-content: space-between;
1887
+ }
1888
+ </style>
1889
+ </head>
1890
+ <body>
1891
+ <div class="header">
1892
+ <div class="title">
1893
+ DevLogger
1894
+ <span class="badge" id="log-count">0</span>
1895
+ </div>
1896
+ <div class="status">
1897
+ <span class="status-dot" id="status-dot"></span>
1898
+ <span id="status-text">Connected</span>
1899
+ </div>
1900
+ <div class="actions">
1901
+ <button class="btn btn-copy" id="btn-copy-json" title="Copy as JSON">JSON</button>
1902
+ <button class="btn btn-copy" id="btn-copy-text" title="Copy as Text">TXT</button>
1903
+ <button class="btn btn-timeline" id="btn-timeline" title="Toggle Timeline">Timeline</button>
1904
+ <button class="btn" id="btn-clear">Clear</button>
1905
+ </div>
1906
+ </div>
1907
+
1908
+ <div class="timeline-container" id="timeline-container" style="display: none;">
1909
+ <canvas id="timeline-canvas"></canvas>
1910
+ <div class="timeline-tooltip" id="timeline-tooltip"></div>
1911
+ <div class="timeline-controls">
1912
+ <button class="timeline-btn" data-window="10000">10s</button>
1913
+ <button class="timeline-btn active" data-window="30000">30s</button>
1914
+ <button class="timeline-btn" data-window="60000">60s</button>
1915
+ </div>
1916
+ </div>
1917
+
1918
+ <div class="filter-bar" id="filter-bar">
1919
+ <div class="filter-row">
1920
+ <div class="filter-levels">
1921
+ <button class="filter-level-btn active" data-level="debug" title="Debug">D</button>
1922
+ <button class="filter-level-btn active" data-level="info" title="Info">I</button>
1923
+ <button class="filter-level-btn active" data-level="warn" title="Warning">W</button>
1924
+ <button class="filter-level-btn active" data-level="error" title="Error">E</button>
1925
+ </div>
1926
+ <input type="text" class="filter-input" id="filter-search" placeholder="Search logs..." data-filter="search">
1927
+ <input type="text" class="filter-input" id="filter-file" placeholder="Filter by file..." data-filter="file" style="max-width: 120px;">
1928
+ </div>
1929
+ <div class="filter-status" id="filter-status" style="display: none;"></div>
1930
+ </div>
1931
+
1932
+ <div class="logs" id="logs">
1933
+ <div class="empty">Waiting for logs...</div>
1934
+ </div>
1935
+
1936
+ <div class="footer">
1937
+ <span id="footer-count">0 logs</span>
1938
+ <span>Pop-out Window</span>
1939
+ </div>
1940
+
1941
+ <script>
1942
+ // Pop-out window script
1943
+ const CHANNEL_NAME = 'devlogger-sync';
1944
+ let logs = [];
1945
+ let channel = null;
1946
+ let isConnected = false;
1947
+
1948
+ // Filter state
1949
+ const filter = {
1950
+ levels: new Set(['debug', 'info', 'warn', 'error']),
1951
+ search: '',
1952
+ file: ''
1953
+ };
1954
+
1955
+ // DOM elements
1956
+ const logsContainer = document.getElementById('logs');
1957
+ const logCountBadge = document.getElementById('log-count');
1958
+ const footerCount = document.getElementById('footer-count');
1959
+ const statusDot = document.getElementById('status-dot');
1960
+ const statusText = document.getElementById('status-text');
1961
+ const btnClear = document.getElementById('btn-clear');
1962
+ const btnCopyJson = document.getElementById('btn-copy-json');
1963
+ const btnCopyText = document.getElementById('btn-copy-text');
1964
+ const btnTimeline = document.getElementById('btn-timeline');
1965
+ const timelineContainer = document.getElementById('timeline-container');
1966
+ const timelineCanvas = document.getElementById('timeline-canvas');
1967
+ const timelineTooltip = document.getElementById('timeline-tooltip');
1968
+ const filterBar = document.getElementById('filter-bar');
1969
+
1970
+ // Timeline state
1971
+ let timelineVisible = false;
1972
+ let timelineWindow = 30000; // 30 seconds default
1973
+ let timelineInterval = null;
1974
+ const LEVEL_COLORS = {
1975
+ debug: '#6e6e6e',
1976
+ info: '#3794ff',
1977
+ warn: '#cca700',
1978
+ error: '#f14c4c'
1979
+ };
1980
+ const filterSearch = document.getElementById('filter-search');
1981
+ const filterFile = document.getElementById('filter-file');
1982
+ const filterStatus = document.getElementById('filter-status');
1983
+
1984
+ // Connect to broadcast channel
1985
+ function connect() {
1986
+ try {
1987
+ channel = new BroadcastChannel(CHANNEL_NAME);
1988
+ channel.onmessage = handleMessage;
1989
+ isConnected = true;
1990
+ updateStatus(true);
1991
+
1992
+ // Request sync from main window
1993
+ channel.postMessage({
1994
+ type: 'SYNC_REQUEST',
1995
+ senderId: 'popout',
1996
+ timestamp: Date.now()
1997
+ });
1998
+ } catch (e) {
1999
+ console.error('Failed to connect:', e);
2000
+ updateStatus(false);
2001
+ }
2002
+ }
2003
+
2004
+ // Handle incoming messages
2005
+ function handleMessage(event) {
2006
+ const message = event.data;
2007
+
2008
+ switch (message.type) {
2009
+ case 'NEW_LOG':
2010
+ addLog(message.payload);
2011
+ break;
2012
+ case 'SYNC_RESPONSE':
2013
+ syncLogs(message.payload);
2014
+ break;
2015
+ case 'CLEAR_LOGS':
2016
+ clearLogs();
2017
+ break;
2018
+ }
2019
+ }
2020
+
2021
+ // Add a single log
2022
+ function addLog(log) {
2023
+ logs.push(log);
2024
+ // Only render if log matches current filter
2025
+ if (matchesFilter(log)) {
2026
+ // Remove empty state if present
2027
+ const empty = logsContainer.querySelector('.empty');
2028
+ if (empty) empty.remove();
2029
+ renderLog(log);
2030
+ scrollToBottom();
2031
+ }
2032
+ updateCounts();
2033
+ updateFilterUI();
2034
+ }
2035
+
2036
+ // Sync all logs
2037
+ function syncLogs(newLogs) {
2038
+ logs = newLogs || [];
2039
+ renderAllLogs();
2040
+ updateCounts();
2041
+ }
2042
+
2043
+ // Clear all logs
2044
+ function clearLogs() {
2045
+ logs = [];
2046
+ renderAllLogs();
2047
+ updateCounts();
2048
+ }
2049
+
2050
+ // Check if a log matches the current filter
2051
+ function matchesFilter(log) {
2052
+ // Level filter
2053
+ if (filter.levels.size > 0 && !filter.levels.has(log.level)) {
2054
+ return false;
2055
+ }
2056
+
2057
+ // File filter
2058
+ if (filter.file && !log.source.file.toLowerCase().includes(filter.file.toLowerCase())) {
2059
+ return false;
2060
+ }
2061
+
2062
+ // Text search
2063
+ if (filter.search) {
2064
+ const searchLower = filter.search.toLowerCase();
2065
+ const messageMatch = log.message.toLowerCase().includes(searchLower);
2066
+ const dataMatch = JSON.stringify(log.data).toLowerCase().includes(searchLower);
2067
+ if (!messageMatch && !dataMatch) {
2068
+ return false;
2069
+ }
2070
+ }
2071
+
2072
+ return true;
2073
+ }
2074
+
2075
+ // Get filtered logs
2076
+ function getFilteredLogs() {
2077
+ return logs.filter(log => matchesFilter(log));
2078
+ }
2079
+
2080
+ // Check if any filter is active
2081
+ function isFilterActive() {
2082
+ return filter.levels.size !== 4 || filter.search !== '' || filter.file !== '';
2083
+ }
2084
+
2085
+ // Update filter UI
2086
+ function updateFilterUI() {
2087
+ const active = isFilterActive();
2088
+ const filteredLogs = getFilteredLogs();
2089
+
2090
+ filterBar.classList.toggle('filter-active', active);
2091
+
2092
+ if (active) {
2093
+ filterStatus.style.display = 'block';
2094
+ filterStatus.textContent = 'Showing ' + filteredLogs.length + ' of ' + logs.length + ' logs';
2095
+ } else {
2096
+ filterStatus.style.display = 'none';
2097
+ }
2098
+
2099
+ // Update counts
2100
+ logCountBadge.textContent = filteredLogs.length;
2101
+ if (active) {
2102
+ footerCount.textContent = filteredLogs.length + ' of ' + logs.length + ' logs';
2103
+ } else {
2104
+ footerCount.textContent = logs.length + ' logs';
2105
+ }
2106
+ }
2107
+
2108
+ // Render all logs
2109
+ function renderAllLogs() {
2110
+ const filteredLogs = getFilteredLogs();
2111
+
2112
+ if (logs.length === 0) {
2113
+ logsContainer.innerHTML = '<div class="empty">No logs yet...</div>';
2114
+ updateFilterUI();
2115
+ return;
2116
+ }
2117
+
2118
+ if (filteredLogs.length === 0) {
2119
+ logsContainer.innerHTML = '<div class="empty">No logs match your filter</div>';
2120
+ updateFilterUI();
2121
+ return;
2122
+ }
2123
+
2124
+ logsContainer.innerHTML = '';
2125
+ filteredLogs.forEach(log => renderLog(log));
2126
+ scrollToBottom();
2127
+ updateFilterUI();
2128
+ }
2129
+
2130
+ // Render a single log entry
2131
+ function renderLog(log) {
2132
+ // Remove empty state if present
2133
+ const empty = logsContainer.querySelector('.empty');
2134
+ if (empty) empty.remove();
2135
+
2136
+ const entry = document.createElement('div');
2137
+ entry.className = 'log-entry';
2138
+
2139
+ const time = formatTime(log.timestamp);
2140
+ const source = formatSource(log.source);
2141
+ const hasData = log.data && log.data.length > 0;
2142
+ const dataId = 'data-' + log.id;
2143
+
2144
+ entry.innerHTML = \`
2145
+ <div class="log-header">
2146
+ <span class="log-level log-level-\${log.level}">\${log.level}</span>
2147
+ <span class="log-time">\${time}</span>
2148
+ <span class="log-source">\${escapeHtml(source)}</span>
2149
+ </div>
2150
+ <div class="log-message">\${escapeHtml(log.message)}</div>
2151
+ \${hasData ? \`
2152
+ <div class="log-data">
2153
+ <button class="log-data-toggle" data-target="\${dataId}">
2154
+ \${log.data.length} item\${log.data.length > 1 ? 's' : ''}
2155
+ </button>
2156
+ <pre class="log-data-content" id="\${dataId}">\${escapeHtml(formatData(log.data))}</pre>
2157
+ </div>
2158
+ \` : ''}
2159
+ \`;
2160
+
2161
+ // Add toggle handler
2162
+ if (hasData) {
2163
+ const toggle = entry.querySelector('.log-data-toggle');
2164
+ const content = entry.querySelector('#' + dataId);
2165
+ toggle.addEventListener('click', () => {
2166
+ toggle.classList.toggle('expanded');
2167
+ content.classList.toggle('visible');
2168
+ });
2169
+ }
2170
+
2171
+ logsContainer.appendChild(entry);
2172
+ }
2173
+
2174
+ // Format timestamp
2175
+ function formatTime(timestamp) {
2176
+ const date = new Date(timestamp);
2177
+ return date.toTimeString().split(' ')[0] + '.' +
2178
+ date.getMilliseconds().toString().padStart(3, '0');
2179
+ }
2180
+
2181
+ // Format source
2182
+ function formatSource(source) {
2183
+ if (!source || source.file === 'unknown') return 'unknown';
2184
+ return source.file + ':' + source.line;
2185
+ }
2186
+
2187
+ // Format data
2188
+ function formatData(data) {
2189
+ try {
2190
+ return data.map(item =>
2191
+ typeof item === 'string' ? item : JSON.stringify(item, null, 2)
2192
+ ).join('\\n');
2193
+ } catch {
2194
+ return '[Unable to display]';
2195
+ }
2196
+ }
2197
+
2198
+ // Escape HTML
2199
+ function escapeHtml(str) {
2200
+ const div = document.createElement('div');
2201
+ div.textContent = str;
2202
+ return div.innerHTML;
2203
+ }
2204
+
2205
+ // Update counts
2206
+ function updateCounts() {
2207
+ const count = logs.length;
2208
+ logCountBadge.textContent = count;
2209
+ footerCount.textContent = count + ' logs';
2210
+ }
2211
+
2212
+ // Update connection status
2213
+ function updateStatus(connected) {
2214
+ isConnected = connected;
2215
+ statusDot.className = 'status-dot' + (connected ? '' : ' disconnected');
2216
+ statusText.textContent = connected ? 'Connected' : 'Disconnected';
2217
+ }
2218
+
2219
+ // Scroll to bottom
2220
+ function scrollToBottom() {
2221
+ logsContainer.scrollTop = logsContainer.scrollHeight;
2222
+ }
2223
+
2224
+ // Clear button handler
2225
+ btnClear.addEventListener('click', () => {
2226
+ if (channel) {
2227
+ channel.postMessage({
2228
+ type: 'CLEAR_LOGS',
2229
+ senderId: 'popout',
2230
+ timestamp: Date.now()
2231
+ });
2232
+ }
2233
+ clearLogs();
2234
+ });
2235
+
2236
+ // Export logs as JSON
2237
+ function exportLogsJson() {
2238
+ return JSON.stringify(logs, null, 2);
2239
+ }
2240
+
2241
+ // Export logs as text
2242
+ function exportLogsText() {
2243
+ return logs.map(log => {
2244
+ const time = new Date(log.timestamp).toISOString();
2245
+ const level = log.level.toUpperCase().padEnd(5);
2246
+ const source = log.source.file + ':' + log.source.line;
2247
+ const context = log.context ? ' [' + Object.entries(log.context).map(([k, v]) => k + '=' + v).join(', ') + ']' : '';
2248
+ const span = log.spanId ? ' (span: ' + log.spanId + ')' : '';
2249
+ const data = log.data.length > 0 ? '\\n Data: ' + JSON.stringify(log.data) : '';
2250
+ return '[' + time + '] ' + level + ' ' + log.message + context + span + '\\n Source: ' + source + data;
2251
+ }).join('\\n\\n');
2252
+ }
2253
+
2254
+ // Show copy feedback
2255
+ function showCopyFeedback(button, success) {
2256
+ const originalText = button.textContent;
2257
+ button.textContent = success ? '✓' : '✗';
2258
+ button.classList.add(success ? 'success' : 'error');
2259
+ setTimeout(() => {
2260
+ button.textContent = originalText;
2261
+ button.classList.remove('success', 'error');
2262
+ }, 1500);
2263
+ }
2264
+
2265
+ // Copy JSON handler
2266
+ btnCopyJson.addEventListener('click', () => {
2267
+ navigator.clipboard.writeText(exportLogsJson()).then(() => {
2268
+ showCopyFeedback(btnCopyJson, true);
2269
+ }).catch(() => {
2270
+ showCopyFeedback(btnCopyJson, false);
2271
+ });
2272
+ });
2273
+
2274
+ // Copy Text handler
2275
+ btnCopyText.addEventListener('click', () => {
2276
+ navigator.clipboard.writeText(exportLogsText()).then(() => {
2277
+ showCopyFeedback(btnCopyText, true);
2278
+ }).catch(() => {
2279
+ showCopyFeedback(btnCopyText, false);
2280
+ });
2281
+ });
2282
+
2283
+ // Check connection periodically
2284
+ setInterval(() => {
2285
+ if (channel) {
2286
+ try {
2287
+ channel.postMessage({
2288
+ type: 'PING',
2289
+ senderId: 'popout',
2290
+ timestamp: Date.now()
2291
+ });
2292
+ } catch {
2293
+ updateStatus(false);
2294
+ }
2295
+ }
2296
+ }, 5000);
2297
+
2298
+ // Filter: Level buttons
2299
+ document.querySelectorAll('.filter-level-btn').forEach(btn => {
2300
+ btn.addEventListener('click', (e) => {
2301
+ const level = e.currentTarget.dataset.level;
2302
+ if (filter.levels.has(level)) {
2303
+ filter.levels.delete(level);
2304
+ e.currentTarget.classList.remove('active');
2305
+ } else {
2306
+ filter.levels.add(level);
2307
+ e.currentTarget.classList.add('active');
2308
+ }
2309
+ renderAllLogs();
2310
+ });
2311
+ });
2312
+
2313
+ // Filter: Search input
2314
+ filterSearch.addEventListener('input', (e) => {
2315
+ filter.search = e.target.value;
2316
+ renderAllLogs();
2317
+ });
2318
+
2319
+ // Filter: File input
2320
+ filterFile.addEventListener('input', (e) => {
2321
+ filter.file = e.target.value;
2322
+ renderAllLogs();
2323
+ });
2324
+
2325
+ // Timeline: Toggle button
2326
+ btnTimeline.addEventListener('click', () => {
2327
+ timelineVisible = !timelineVisible;
2328
+ timelineContainer.style.display = timelineVisible ? 'block' : 'none';
2329
+ btnTimeline.classList.toggle('active', timelineVisible);
2330
+ if (timelineVisible) {
2331
+ resizeCanvas();
2332
+ drawTimeline();
2333
+ startTimelineRefresh();
2334
+ } else {
2335
+ stopTimelineRefresh();
2336
+ }
2337
+ });
2338
+
2339
+ // Timeline: Time window buttons
2340
+ document.querySelectorAll('.timeline-btn[data-window]').forEach(btn => {
2341
+ btn.addEventListener('click', (e) => {
2342
+ timelineWindow = parseInt(e.currentTarget.dataset.window);
2343
+ document.querySelectorAll('.timeline-btn[data-window]').forEach(b => b.classList.remove('active'));
2344
+ e.currentTarget.classList.add('active');
2345
+ drawTimeline();
2346
+ });
2347
+ });
2348
+
2349
+ // Timeline: Resize canvas for proper DPI
2350
+ function resizeCanvas() {
2351
+ const rect = timelineCanvas.getBoundingClientRect();
2352
+ const dpr = window.devicePixelRatio || 1;
2353
+ timelineCanvas.width = rect.width * dpr;
2354
+ timelineCanvas.height = rect.height * dpr;
2355
+ const ctx = timelineCanvas.getContext('2d');
2356
+ ctx.scale(dpr, dpr);
2357
+ }
2358
+
2359
+ // Timeline: Draw the timeline visualization
2360
+ function drawTimeline() {
2361
+ const ctx = timelineCanvas.getContext('2d');
2362
+ const rect = timelineCanvas.getBoundingClientRect();
2363
+ const width = rect.width;
2364
+ const height = rect.height;
2365
+
2366
+ // Clear canvas
2367
+ ctx.fillStyle = '#1e1e1e';
2368
+ ctx.fillRect(0, 0, width, height);
2369
+
2370
+ // Calculate time bounds
2371
+ const now = Date.now();
2372
+ const startTime = now - timelineWindow;
2373
+
2374
+ // Filter logs within time window
2375
+ const visibleLogs = logs.filter(log => log.timestamp >= startTime && log.timestamp <= now);
2376
+
2377
+ // Draw time grid
2378
+ ctx.strokeStyle = '#333';
2379
+ ctx.lineWidth = 1;
2380
+ const gridLines = 5;
2381
+ for (let i = 0; i <= gridLines; i++) {
2382
+ const x = (i / gridLines) * width;
2383
+ ctx.beginPath();
2384
+ ctx.moveTo(x, 0);
2385
+ ctx.lineTo(x, height);
2386
+ ctx.stroke();
2387
+
2388
+ // Draw time labels
2389
+ const time = new Date(startTime + (i / gridLines) * timelineWindow);
2390
+ ctx.fillStyle = '#666';
2391
+ ctx.font = '10px monospace';
2392
+ ctx.fillText(time.toTimeString().split(' ')[0], x + 3, height - 5);
2393
+ }
2394
+
2395
+ // Draw log markers
2396
+ const markerHeight = 16;
2397
+ const bottomOffset = 20;
2398
+
2399
+ visibleLogs.forEach(log => {
2400
+ const x = ((log.timestamp - startTime) / timelineWindow) * width;
2401
+ const y = height - bottomOffset - markerHeight / 2;
2402
+ const color = LEVEL_COLORS[log.level] || LEVEL_COLORS.debug;
2403
+
2404
+ // Draw marker
2405
+ ctx.beginPath();
2406
+ ctx.arc(x, y, 4, 0, Math.PI * 2);
2407
+ ctx.fillStyle = color;
2408
+ ctx.fill();
2409
+
2410
+ // Store bounds for hover
2411
+ log._timelineBounds = { x, y, r: 8 };
2412
+ });
2413
+
2414
+ // Draw level legend
2415
+ ctx.font = '10px monospace';
2416
+ let legendX = 10;
2417
+ Object.entries(LEVEL_COLORS).forEach(([level, color]) => {
2418
+ ctx.fillStyle = color;
2419
+ ctx.beginPath();
2420
+ ctx.arc(legendX, 12, 4, 0, Math.PI * 2);
2421
+ ctx.fill();
2422
+ ctx.fillStyle = '#888';
2423
+ ctx.fillText(level, legendX + 8, 15);
2424
+ legendX += ctx.measureText(level).width + 20;
2425
+ });
2426
+ }
2427
+
2428
+ // Timeline: Mouse move for tooltips
2429
+ timelineCanvas.addEventListener('mousemove', (e) => {
2430
+ const rect = timelineCanvas.getBoundingClientRect();
2431
+ const x = e.clientX - rect.left;
2432
+ const y = e.clientY - rect.top;
2433
+
2434
+ const hoveredLog = logs.find(log => {
2435
+ if (!log._timelineBounds) return false;
2436
+ const b = log._timelineBounds;
2437
+ const dx = x - b.x;
2438
+ const dy = y - b.y;
2439
+ return Math.sqrt(dx * dx + dy * dy) <= b.r;
2440
+ });
2441
+
2442
+ if (hoveredLog) {
2443
+ const time = new Date(hoveredLog.timestamp).toTimeString().split(' ')[0];
2444
+ timelineTooltip.innerHTML =
2445
+ '<strong>[' + hoveredLog.level.toUpperCase() + ']</strong> ' +
2446
+ escapeHtml(hoveredLog.message.substring(0, 100)) +
2447
+ (hoveredLog.message.length > 100 ? '...' : '') +
2448
+ '<br><small>' + time + '</small>';
2449
+ timelineTooltip.style.display = 'block';
2450
+ timelineTooltip.style.left = Math.min(e.clientX - rect.left + 10, rect.width - 260) + 'px';
2451
+ timelineTooltip.style.top = (e.clientY - rect.top - 50) + 'px';
2452
+ } else {
2453
+ timelineTooltip.style.display = 'none';
2454
+ }
2455
+ });
2456
+
2457
+ // Timeline: Hide tooltip on mouse leave
2458
+ timelineCanvas.addEventListener('mouseleave', () => {
2459
+ timelineTooltip.style.display = 'none';
2460
+ });
2461
+
2462
+ // Timeline: Start auto-refresh
2463
+ function startTimelineRefresh() {
2464
+ if (timelineInterval) return;
2465
+ timelineInterval = setInterval(() => {
2466
+ if (timelineVisible) drawTimeline();
2467
+ }, 500);
2468
+ }
2469
+
2470
+ // Timeline: Stop auto-refresh
2471
+ function stopTimelineRefresh() {
2472
+ if (timelineInterval) {
2473
+ clearInterval(timelineInterval);
2474
+ timelineInterval = null;
2475
+ }
2476
+ }
2477
+
2478
+ // Handle window resize for timeline
2479
+ window.addEventListener('resize', () => {
2480
+ if (timelineVisible) {
2481
+ resizeCanvas();
2482
+ drawTimeline();
2483
+ }
2484
+ });
2485
+
2486
+ // Initialize
2487
+ connect();
2488
+ <\/script>
2489
+ </body>
2490
+ </html>`;
2491
+ }
2492
+ function z() {
2493
+ return {
2494
+ levels: /* @__PURE__ */ new Set(["debug", "info", "warn", "error"]),
2495
+ search: "",
2496
+ file: ""
2497
+ };
2498
+ }
2499
+ function nt(t, e) {
2500
+ if (e.levels.size > 0 && !e.levels.has(t.level) || e.file && !t.source.file.toLowerCase().includes(e.file.toLowerCase()))
2501
+ return !1;
2502
+ if (e.search) {
2503
+ const n = e.search.toLowerCase(), o = t.message.toLowerCase().includes(n), r = JSON.stringify(t.data).toLowerCase().includes(n);
2504
+ if (!o && !r)
2505
+ return !1;
2506
+ }
2507
+ return !0;
2508
+ }
2509
+ function U(t, e) {
2510
+ return t.filter((n) => nt(n, e));
2511
+ }
2512
+ function te(t) {
2513
+ return !(t.levels.size === 4) || t.search !== "" || t.file !== "";
2514
+ }
2515
+ function ot(t, e, n) {
2516
+ const o = te(t);
2517
+ return `
2518
+ <div class="filter-bar ${o ? "filter-active" : ""}">
2519
+ <div class="filter-row">
2520
+ <div class="filter-levels">
2521
+ <button class="filter-level-btn ${t.levels.has("debug") ? "active" : ""}" data-level="debug" title="Debug">D</button>
2522
+ <button class="filter-level-btn ${t.levels.has("info") ? "active" : ""}" data-level="info" title="Info">I</button>
2523
+ <button class="filter-level-btn ${t.levels.has("warn") ? "active" : ""}" data-level="warn" title="Warning">W</button>
2524
+ <button class="filter-level-btn ${t.levels.has("error") ? "active" : ""}" data-level="error" title="Error">E</button>
2525
+ </div>
2526
+ <div class="filter-search">
2527
+ <input type="text" class="filter-input" placeholder="Search logs..." value="${pe(t.search)}" data-filter="search">
2528
+ </div>
2529
+ <div class="filter-file">
2530
+ <input type="text" class="filter-input filter-file-input" placeholder="Filter by file..." value="${pe(t.file)}" data-filter="file">
2531
+ </div>
2532
+ ${o ? '<button class="filter-clear-btn" title="Clear filters">✕</button>' : ""}
2533
+ </div>
2534
+ ${o ? `<div class="filter-status">Showing ${n} of ${e} logs</div>` : ""}
2535
+ </div>
2536
+ `;
2537
+ }
2538
+ function pe(t) {
2539
+ return t.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
2540
+ }
2541
+ const Q = { key: "l", ctrlKey: !0, shiftKey: !0 };
2542
+ function he(t, e) {
2543
+ const n = t.textContent;
2544
+ t.textContent = e ? "✓" : "✗", t.disabled = !0, setTimeout(() => {
2545
+ t.textContent = n, t.disabled = !1;
2546
+ }, 1e3);
2547
+ }
2548
+ const i = {
2549
+ initialized: !1,
2550
+ visible: !1,
2551
+ host: null,
2552
+ shadow: null,
2553
+ container: null,
2554
+ logsList: null,
2555
+ filterBar: null,
2556
+ toggleBtn: null,
2557
+ badge: null,
2558
+ unsubscribe: null,
2559
+ channelUnsubscribe: null,
2560
+ filter: z()
2561
+ };
2562
+ function rt(t) {
2563
+ const e = document.createElement("style");
2564
+ e.textContent = qe, t.appendChild(e);
2565
+ const n = document.createElement("button");
2566
+ n.className = "devlogger-toggle", n.innerHTML = "📋", n.title = "Toggle DevLogger (Ctrl+Shift+L)", n.addEventListener("click", () => j.toggle()), t.appendChild(n), i.toggleBtn = n;
2567
+ const o = document.createElement("div");
2568
+ o.className = "devlogger-container hidden";
2569
+ const r = u.getLogs(), s = U(r, i.filter);
2570
+ o.innerHTML = `
2571
+ <div class="devlogger-header">
2572
+ <div class="devlogger-title">
2573
+ DevLogger
2574
+ <span class="devlogger-badge">${s.length}</span>
2575
+ </div>
2576
+ <div class="devlogger-actions">
2577
+ <button class="devlogger-btn" data-action="copy-json" title="Copy as JSON">JSON</button>
2578
+ <button class="devlogger-btn" data-action="copy-text" title="Copy as Text">TXT</button>
2579
+ <button class="devlogger-btn" data-action="clear" title="Clear logs">Clear</button>
2580
+ <button class="devlogger-btn devlogger-btn-primary" data-action="popout" title="Open in new window">Pop-out</button>
2581
+ <button class="devlogger-btn" data-action="close" title="Close (Ctrl+Shift+L)">✕</button>
2582
+ </div>
2583
+ </div>
2584
+ <div class="filter-bar-container"></div>
2585
+ <div class="devlogger-logs"></div>
2586
+ <div class="devlogger-footer">
2587
+ <span class="devlogger-log-count">${r.length} logs</span>
2588
+ <span class="devlogger-shortcut">Ctrl+Shift+L to toggle</span>
2589
+ </div>
2590
+ `, i.badge = o.querySelector(".devlogger-badge"), i.logsList = o.querySelector(".devlogger-logs"), i.filterBar = o.querySelector(".filter-bar-container"), o.querySelectorAll("[data-action]").forEach((c) => {
2591
+ c.addEventListener("click", (l) => {
2592
+ const a = l.currentTarget.dataset.action, d = l.currentTarget;
2593
+ switch (a) {
2594
+ case "clear":
2595
+ u.clear(), H.sendClear(), y();
2596
+ break;
2597
+ case "popout":
2598
+ j.popout();
2599
+ break;
2600
+ case "close":
2601
+ j.close();
2602
+ break;
2603
+ case "copy-json":
2604
+ u.copyLogs({ format: "json" }).then((f) => {
2605
+ he(d, f);
2606
+ });
2607
+ break;
2608
+ case "copy-text":
2609
+ u.copyLogs({ format: "text" }).then((f) => {
2610
+ he(d, f);
2611
+ });
2612
+ break;
2613
+ }
2614
+ });
2615
+ }), t.appendChild(o), i.container = o, I(), y();
2616
+ }
2617
+ function I() {
2618
+ if (!i.filterBar) return;
2619
+ const t = u.getLogs(), e = U(t, i.filter);
2620
+ i.filterBar.innerHTML = ot(i.filter, t.length, e.length), it();
2621
+ }
2622
+ function it() {
2623
+ if (!i.filterBar) return;
2624
+ i.filterBar.querySelectorAll(".filter-level-btn").forEach((o) => {
2625
+ o.addEventListener("click", (r) => {
2626
+ const s = r.currentTarget.dataset.level;
2627
+ i.filter.levels.has(s) ? i.filter.levels.delete(s) : i.filter.levels.add(s), I(), y();
2628
+ });
2629
+ });
2630
+ const t = i.filterBar.querySelector('[data-filter="search"]');
2631
+ t && t.addEventListener("input", (o) => {
2632
+ i.filter.search = o.target.value, Z(), y();
2633
+ });
2634
+ const e = i.filterBar.querySelector('[data-filter="file"]');
2635
+ e && e.addEventListener("input", (o) => {
2636
+ i.filter.file = o.target.value, Z(), y();
2637
+ });
2638
+ const n = i.filterBar.querySelector(".filter-clear-btn");
2639
+ n && n.addEventListener("click", () => {
2640
+ i.filter = z(), I(), y();
2641
+ });
2642
+ }
2643
+ function y() {
2644
+ if (!i.logsList) return;
2645
+ const t = u.getLogs(), e = U(t, i.filter);
2646
+ if (i.logsList.innerHTML = "", t.length === 0)
2647
+ i.logsList.appendChild(Xe());
2648
+ else if (e.length === 0) {
2649
+ const n = document.createElement("div");
2650
+ n.className = "devlogger-no-results", n.innerHTML = `
2651
+ <span class="devlogger-no-results-icon">🔍</span>
2652
+ <span class="devlogger-no-results-text">No logs match your filter</span>
2653
+ `, i.logsList.appendChild(n);
2654
+ } else {
2655
+ const n = document.createDocumentFragment();
2656
+ for (const o of e)
2657
+ n.appendChild(xe(o));
2658
+ i.logsList.appendChild(n);
2659
+ }
2660
+ we(e.length, t.length), ne();
2661
+ }
2662
+ function st(t) {
2663
+ if (!i.logsList) return;
2664
+ const e = u.getLogs(), n = U(e, i.filter);
2665
+ if (n.some((r) => r.id === t.id)) {
2666
+ const r = i.logsList.querySelector(".devlogger-empty, .devlogger-no-results");
2667
+ r && r.remove(), i.logsList.appendChild(xe(t));
2668
+ }
2669
+ we(n.length, e.length), Z(), ne();
2670
+ }
2671
+ function we(t, e) {
2672
+ i.badge && (i.badge.textContent = String(t));
2673
+ const n = i.container?.querySelector(".devlogger-log-count");
2674
+ n && (te(i.filter) ? n.textContent = `${t} of ${e} logs` : n.textContent = `${e} logs`);
2675
+ }
2676
+ function Z() {
2677
+ if (!i.filterBar) return;
2678
+ const t = u.getLogs(), e = U(t, i.filter), n = te(i.filter), o = i.filterBar.querySelector(".filter-bar");
2679
+ o && o.classList.toggle("filter-active", n);
2680
+ let r = i.filterBar.querySelector(".filter-status");
2681
+ if (n) {
2682
+ r || (r = document.createElement("div"), r.className = "filter-status", o?.appendChild(r)), r.textContent = `Showing ${e.length} of ${t.length} logs`;
2683
+ let s = i.filterBar.querySelector(".filter-clear-btn");
2684
+ s || (s = document.createElement("button"), s.className = "filter-clear-btn", s.setAttribute("title", "Clear filters"), s.textContent = "✕", s.addEventListener("click", () => {
2685
+ i.filter = z(), I(), y();
2686
+ }), i.filterBar.querySelector(".filter-row")?.appendChild(s));
2687
+ } else
2688
+ r?.remove(), i.filterBar.querySelector(".filter-clear-btn")?.remove();
2689
+ }
2690
+ function ne() {
2691
+ i.logsList && i.visible && (i.logsList.scrollTop = i.logsList.scrollHeight);
2692
+ }
2693
+ function be(t) {
2694
+ t.key.toLowerCase() === Q.key && t.ctrlKey === Q.ctrlKey && t.shiftKey === Q.shiftKey && (t.preventDefault(), j.toggle());
2695
+ }
2696
+ function at(t) {
2697
+ switch (t.type) {
2698
+ case "CLEAR_LOGS":
2699
+ u.clear(), y();
2700
+ break;
2701
+ case "SYNC_REQUEST":
2702
+ H.sendSyncResponse(u.getLogs());
2703
+ break;
2704
+ }
2705
+ }
2706
+ const j = {
2707
+ /**
2708
+ * Initialize the overlay UI
2709
+ */
2710
+ init() {
2711
+ try {
2712
+ if (i.initialized || !u.isEnabled() || typeof document > "u")
2713
+ return;
2714
+ const t = document.createElement("div");
2715
+ t.id = "devlogger-host", document.body.appendChild(t), i.host = t;
2716
+ const e = t.attachShadow({ mode: "open" });
2717
+ i.shadow = e, rt(e), i.unsubscribe = u.subscribe((n) => {
2718
+ st(n), H.sendLog(n);
2719
+ }), i.channelUnsubscribe = H.subscribe(at), document.addEventListener("keydown", be), i.initialized = !0;
2720
+ } catch (t) {
2721
+ console.warn("[DevLogger UI] Init error:", t);
2722
+ }
2723
+ },
2724
+ /**
2725
+ * Open the overlay panel
2726
+ */
2727
+ open() {
2728
+ try {
2729
+ i.initialized || this.init(), i.container && (i.container.classList.remove("hidden"), i.visible = !0, ne());
2730
+ } catch {
2731
+ }
2732
+ },
2733
+ /**
2734
+ * Close the overlay panel
2735
+ */
2736
+ close() {
2737
+ try {
2738
+ i.container && (i.container.classList.add("hidden"), i.visible = !1);
2739
+ } catch {
2740
+ }
2741
+ },
2742
+ /**
2743
+ * Toggle the overlay panel
2744
+ */
2745
+ toggle() {
2746
+ i.visible ? this.close() : this.open();
2747
+ },
2748
+ /**
2749
+ * Open logs in a separate window
2750
+ */
2751
+ popout() {
2752
+ try {
2753
+ i.initialized || this.init(), Ze();
2754
+ } catch (t) {
2755
+ console.warn("[DevLogger] Failed to open pop-out:", t);
2756
+ }
2757
+ },
2758
+ /**
2759
+ * Close the pop-out window
2760
+ */
2761
+ closePopout() {
2762
+ ue();
2763
+ },
2764
+ /**
2765
+ * Check if pop-out window is open
2766
+ */
2767
+ isPopoutOpen() {
2768
+ return et();
2769
+ },
2770
+ /**
2771
+ * Set filter state
2772
+ */
2773
+ setFilter(t) {
2774
+ try {
2775
+ t.levels !== void 0 && (i.filter.levels = t.levels), t.search !== void 0 && (i.filter.search = t.search), t.file !== void 0 && (i.filter.file = t.file), I(), y();
2776
+ } catch {
2777
+ }
2778
+ },
2779
+ /**
2780
+ * Get current filter state
2781
+ */
2782
+ getFilter() {
2783
+ return { ...i.filter, levels: new Set(i.filter.levels) };
2784
+ },
2785
+ /**
2786
+ * Clear all filters
2787
+ */
2788
+ clearFilter() {
2789
+ try {
2790
+ i.filter = z(), I(), y();
2791
+ } catch {
2792
+ }
2793
+ },
2794
+ /**
2795
+ * Destroy the UI and clean up
2796
+ */
2797
+ destroy() {
2798
+ try {
2799
+ ue(), i.unsubscribe && (i.unsubscribe(), i.unsubscribe = null), i.channelUnsubscribe && (i.channelUnsubscribe(), i.channelUnsubscribe = null), document.removeEventListener("keydown", be), i.host && (i.host.remove(), i.host = null), i.initialized = !1, i.visible = !1, i.shadow = null, i.container = null, i.logsList = null, i.filterBar = null, i.toggleBtn = null, i.badge = null, i.filter = z();
2800
+ } catch {
2801
+ }
2802
+ },
2803
+ /**
2804
+ * Check if the UI is currently visible
2805
+ */
2806
+ isVisible() {
2807
+ return i.visible;
2808
+ },
2809
+ /**
2810
+ * Check if the UI has been initialized
2811
+ */
2812
+ isInitialized() {
2813
+ return i.initialized;
2814
+ }
2815
+ }, Le = {
2816
+ captureErrors: !0,
2817
+ captureRejections: !0,
2818
+ errorPrefix: "[Uncaught Error]",
2819
+ rejectionPrefix: "[Unhandled Rejection]"
2820
+ };
2821
+ let N = !1, L = { ...Le }, P = null, O = null;
2822
+ function lt(t, e, n, o, r) {
2823
+ try {
2824
+ const s = r || (t instanceof ErrorEvent ? t.error : null), c = s?.message || String(t), l = s?.stack;
2825
+ u.error(`${L.errorPrefix} ${c}`, {
2826
+ source: e || "unknown",
2827
+ line: n || 0,
2828
+ column: o || 0,
2829
+ stack: l,
2830
+ originalError: s ? {
2831
+ name: s.name,
2832
+ message: s.message,
2833
+ stack: s.stack
2834
+ } : void 0
2835
+ });
2836
+ } catch {
2837
+ }
2838
+ if (P)
2839
+ try {
2840
+ return P(t, e, n, o, r) ?? !1;
2841
+ } catch {
2842
+ }
2843
+ return !1;
2844
+ }
2845
+ function ct(t) {
2846
+ try {
2847
+ const e = t.reason;
2848
+ let n, o = {};
2849
+ e instanceof Error ? (n = e.message, o = {
2850
+ name: e.name,
2851
+ message: e.message,
2852
+ stack: e.stack
2853
+ }) : typeof e == "string" ? n = e : (n = "Unknown rejection reason", o = { reason: e }), u.error(`${L.rejectionPrefix} ${n}`, o);
2854
+ } catch {
2855
+ }
2856
+ }
2857
+ function dt(t = {}) {
2858
+ if (N) {
2859
+ L = { ...L, ...t };
2860
+ return;
2861
+ }
2862
+ try {
2863
+ if (typeof window > "u")
2864
+ return;
2865
+ L = { ...Le, ...t }, P = window.onerror, L.captureErrors && (window.onerror = lt), L.captureRejections && (O = ct, window.addEventListener("unhandledrejection", O)), N = !0;
2866
+ } catch {
2867
+ }
2868
+ }
2869
+ function gt() {
2870
+ if (N)
2871
+ try {
2872
+ if (typeof window > "u")
2873
+ return;
2874
+ window.onerror = P, O && window.removeEventListener("unhandledrejection", O), P = null, O = null, N = !1;
2875
+ } catch {
2876
+ }
2877
+ }
2878
+ function ft() {
2879
+ return N;
2880
+ }
2881
+ function ut() {
2882
+ return { ...L };
2883
+ }
2884
+ const Ot = {
2885
+ /**
2886
+ * Install global error handlers
2887
+ *
2888
+ * @example
2889
+ * ```typescript
2890
+ * // Install with defaults
2891
+ * ErrorCapture.install();
2892
+ *
2893
+ * // Install with custom config
2894
+ * ErrorCapture.install({
2895
+ * captureErrors: true,
2896
+ * captureRejections: true,
2897
+ * errorPrefix: '[ERROR]',
2898
+ * });
2899
+ * ```
2900
+ */
2901
+ install: dt,
2902
+ /**
2903
+ * Uninstall global error handlers and restore originals
2904
+ */
2905
+ uninstall: gt,
2906
+ /**
2907
+ * Check if error capture is currently active
2908
+ */
2909
+ isActive: ft,
2910
+ /**
2911
+ * Get current configuration
2912
+ */
2913
+ getConfig: ut
2914
+ }, J = "devlogger_persisted_logs", oe = "devlogger_session_active", Se = {
2915
+ storage: "session",
2916
+ maxPersisted: 500,
2917
+ debounceMs: 100
2918
+ };
2919
+ let R = !1, S = { ...Se }, q = [], x = null, re = !1, W = null;
2920
+ function k() {
2921
+ try {
2922
+ return typeof window > "u" ? null : S.storage === "local" ? localStorage : sessionStorage;
2923
+ } catch {
2924
+ return null;
2925
+ }
2926
+ }
2927
+ function pt() {
2928
+ try {
2929
+ const t = k();
2930
+ return t ? t.getItem(oe) === "active" : !1;
2931
+ } catch {
2932
+ return !1;
2933
+ }
2934
+ }
2935
+ function ht() {
2936
+ try {
2937
+ const t = k();
2938
+ if (!t) return;
2939
+ t.setItem(oe, "active");
2940
+ } catch {
2941
+ }
2942
+ }
2943
+ function Ce() {
2944
+ try {
2945
+ const t = k();
2946
+ if (!t) return;
2947
+ t.removeItem(oe);
2948
+ } catch {
2949
+ }
2950
+ }
2951
+ function ke(t) {
2952
+ try {
2953
+ const e = k();
2954
+ if (!e) return;
2955
+ const n = t.slice(-S.maxPersisted), o = JSON.stringify(n);
2956
+ e.setItem(J, o);
2957
+ } catch (e) {
2958
+ if (e instanceof DOMException && e.name === "QuotaExceededError")
2959
+ try {
2960
+ const n = k();
2961
+ if (!n) return;
2962
+ const o = t.slice(-Math.floor(S.maxPersisted / 2));
2963
+ n.setItem(J, JSON.stringify(o));
2964
+ } catch {
2965
+ }
2966
+ }
2967
+ }
2968
+ function $e() {
2969
+ try {
2970
+ const t = k();
2971
+ if (!t) return [];
2972
+ const e = t.getItem(J);
2973
+ if (!e) return [];
2974
+ const n = JSON.parse(e);
2975
+ return Array.isArray(n) ? n.filter(
2976
+ (o) => o && typeof o.id == "string" && typeof o.timestamp == "number" && typeof o.level == "string" && typeof o.message == "string"
2977
+ ) : [];
2978
+ } catch {
2979
+ return [];
2980
+ }
2981
+ }
2982
+ function bt() {
2983
+ try {
2984
+ const t = k();
2985
+ if (!t) return;
2986
+ t.removeItem(J);
2987
+ } catch {
2988
+ }
2989
+ }
2990
+ function vt(t) {
2991
+ q = t, x && clearTimeout(x), x = setTimeout(() => {
2992
+ ke(q), x = null;
2993
+ }, S.debounceMs);
2994
+ }
2995
+ function X() {
2996
+ try {
2997
+ x && (clearTimeout(x), x = null), q.length > 0 && ke(q), Ce();
2998
+ } catch {
2999
+ }
3000
+ }
3001
+ function mt() {
3002
+ if (!R) return;
3003
+ const t = u.getLogs();
3004
+ vt([...t]);
3005
+ }
3006
+ function yt(t = {}) {
3007
+ if (R) {
3008
+ S = { ...S, ...t };
3009
+ return;
3010
+ }
3011
+ try {
3012
+ if (typeof window > "u")
3013
+ return;
3014
+ S = { ...Se, ...t }, re = pt(), ht(), W = u.subscribe(mt), window.addEventListener("beforeunload", X), window.addEventListener("pagehide", X), R = !0;
3015
+ } catch {
3016
+ }
3017
+ }
3018
+ function xt() {
3019
+ if (R)
3020
+ try {
3021
+ if (typeof window > "u")
3022
+ return;
3023
+ x && (clearTimeout(x), x = null), W && (W(), W = null), window.removeEventListener("beforeunload", X), window.removeEventListener("pagehide", X), Ce(), R = !1;
3024
+ } catch {
3025
+ }
3026
+ }
3027
+ function wt() {
3028
+ return re;
3029
+ }
3030
+ function Lt() {
3031
+ return $e();
3032
+ }
3033
+ function St() {
3034
+ try {
3035
+ const t = $e();
3036
+ return t.length === 0 ? 0 : (u.importLogs(t), t.length);
3037
+ } catch {
3038
+ return 0;
3039
+ }
3040
+ }
3041
+ function Ct() {
3042
+ bt(), q = [], re = !1;
3043
+ }
3044
+ function kt() {
3045
+ return R;
3046
+ }
3047
+ function $t() {
3048
+ return { ...S };
3049
+ }
3050
+ const At = {
3051
+ /**
3052
+ * Enable log persistence
3053
+ *
3054
+ * @example
3055
+ * ```typescript
3056
+ * LogPersistence.enable();
3057
+ *
3058
+ * // With options
3059
+ * LogPersistence.enable({
3060
+ * storage: 'session',
3061
+ * maxPersisted: 500,
3062
+ * debounceMs: 100
3063
+ * });
3064
+ * ```
3065
+ */
3066
+ enable: yt,
3067
+ /**
3068
+ * Disable log persistence
3069
+ */
3070
+ disable: xt,
3071
+ /**
3072
+ * Check if persistence is enabled
3073
+ */
3074
+ isActive: kt,
3075
+ /**
3076
+ * Check if the last session had a crash (unclean shutdown)
3077
+ */
3078
+ hadCrash: wt,
3079
+ /**
3080
+ * Get persisted logs from previous session (without importing)
3081
+ */
3082
+ getPersistedLogs: Lt,
3083
+ /**
3084
+ * Rehydrate logs from storage into the logger
3085
+ * Call this at app startup to restore logs from previous session
3086
+ *
3087
+ * @returns Number of logs rehydrated
3088
+ *
3089
+ * @example
3090
+ * ```typescript
3091
+ * // At app start
3092
+ * LogPersistence.enable();
3093
+ * const count = LogPersistence.rehydrate();
3094
+ * if (LogPersistence.hadCrash()) {
3095
+ * console.log(`Recovered ${count} logs from crash`);
3096
+ * }
3097
+ * ```
3098
+ */
3099
+ rehydrate: St,
3100
+ /**
3101
+ * Clear all persisted logs
3102
+ */
3103
+ clear: Ct,
3104
+ /**
3105
+ * Get current configuration
3106
+ */
3107
+ getConfig: $t
3108
+ }, Ee = {
3109
+ captureFetch: !0,
3110
+ captureXHR: !0,
3111
+ includeHeaders: !1,
3112
+ includeBody: !1,
3113
+ includeResponse: !1,
3114
+ maxResponseLength: 1e3,
3115
+ ignorePatterns: [],
3116
+ context: {}
3117
+ };
3118
+ let E = null, A = null, T = null, B = !1, v = { ...Ee };
3119
+ function Te(t) {
3120
+ for (const e of v.ignorePatterns)
3121
+ if (typeof e == "string") {
3122
+ if (t.includes(e)) return !0;
3123
+ } else if (e.test(t))
3124
+ return !0;
3125
+ return !1;
3126
+ }
3127
+ function Ie(t) {
3128
+ try {
3129
+ const e = new URL(t, window.location.origin);
3130
+ return {
3131
+ host: e.host,
3132
+ path: e.pathname + e.search,
3133
+ full: e.href
3134
+ };
3135
+ } catch {
3136
+ return { host: "unknown", path: t, full: t };
3137
+ }
3138
+ }
3139
+ function Re(t, e) {
3140
+ return t.length <= e ? t : t.slice(0, e) + "... (truncated)";
3141
+ }
3142
+ function Y(t) {
3143
+ try {
3144
+ return JSON.parse(t);
3145
+ } catch {
3146
+ return t;
3147
+ }
3148
+ }
3149
+ function Et() {
3150
+ return async function(e, n) {
3151
+ const o = typeof e == "string" ? e : e instanceof URL ? e.href : e.url;
3152
+ if (Te(o))
3153
+ return E(e, n);
3154
+ const { host: r, path: s } = Ie(o), c = n?.method || "GET", l = `${c} ${s}`, a = u.span(l, {
3155
+ ...v.context,
3156
+ type: "fetch",
3157
+ method: c,
3158
+ host: r
3159
+ });
3160
+ if (a.info(`Request started: ${o}`), v.includeHeaders && n?.headers && a.debug("Request headers", n.headers), v.includeBody && n?.body)
3161
+ try {
3162
+ const f = typeof n.body == "string" ? Y(n.body) : n.body;
3163
+ a.debug("Request body", f);
3164
+ } catch {
3165
+ a.debug("Request body", "[Unable to parse]");
3166
+ }
3167
+ const d = performance.now();
3168
+ try {
3169
+ const f = await E(e, n), h = Math.round(performance.now() - d);
3170
+ if (f.ok) {
3171
+ if (a.info(`Response: ${f.status} ${f.statusText} (${h}ms)`), v.includeResponse)
3172
+ try {
3173
+ const K = await f.clone().text(), m = Y(Re(K, v.maxResponseLength));
3174
+ a.debug("Response body", m);
3175
+ } catch {
3176
+ a.debug("Response body", "[Unable to read]");
3177
+ }
3178
+ a.end();
3179
+ } else
3180
+ a.warn(`Response: ${f.status} ${f.statusText} (${h}ms)`), a.fail();
3181
+ return f;
3182
+ } catch (f) {
3183
+ const h = Math.round(performance.now() - d);
3184
+ throw a.error(`Request failed after ${h}ms`, f), a.fail(), f;
3185
+ }
3186
+ };
3187
+ }
3188
+ function Tt() {
3189
+ A = XMLHttpRequest.prototype.open, T = XMLHttpRequest.prototype.send, XMLHttpRequest.prototype.open = function(t, e, n = !0, o, r) {
3190
+ const s = typeof e == "string" ? e : e.href;
3191
+ return this.__networkCapture = {
3192
+ method: t,
3193
+ url: s,
3194
+ ignored: Te(s)
3195
+ }, A.call(this, t, e, n, o, r);
3196
+ }, XMLHttpRequest.prototype.send = function(t) {
3197
+ const e = this.__networkCapture;
3198
+ if (!e || e.ignored)
3199
+ return T.call(this, t);
3200
+ const { method: n, url: o } = e, { host: r, path: s } = Ie(o), c = `${n} ${s}`, l = u.span(c, {
3201
+ ...v.context,
3202
+ type: "xhr",
3203
+ method: n,
3204
+ host: r
3205
+ });
3206
+ if (l.info(`XHR Request started: ${o}`), v.includeBody && t)
3207
+ try {
3208
+ const d = typeof t == "string" ? Y(t) : t;
3209
+ l.debug("Request body", d);
3210
+ } catch {
3211
+ l.debug("Request body", "[Unable to parse]");
3212
+ }
3213
+ const a = performance.now();
3214
+ return this.addEventListener("load", () => {
3215
+ const d = Math.round(performance.now() - a);
3216
+ if (this.status >= 200 && this.status < 400) {
3217
+ if (l.info(`Response: ${this.status} ${this.statusText} (${d}ms)`), v.includeResponse && this.responseText) {
3218
+ const f = Y(Re(this.responseText, v.maxResponseLength));
3219
+ l.debug("Response body", f);
3220
+ }
3221
+ l.end();
3222
+ } else
3223
+ l.warn(`Response: ${this.status} ${this.statusText} (${d}ms)`), l.fail();
3224
+ }), this.addEventListener("error", () => {
3225
+ const d = Math.round(performance.now() - a);
3226
+ l.error(`XHR Request failed after ${d}ms`), l.fail();
3227
+ }), this.addEventListener("abort", () => {
3228
+ const d = Math.round(performance.now() - a);
3229
+ l.warn(`XHR Request aborted after ${d}ms`), l.fail();
3230
+ }), this.addEventListener("timeout", () => {
3231
+ const d = Math.round(performance.now() - a);
3232
+ l.error(`XHR Request timeout after ${d}ms`), l.fail();
3233
+ }), T.call(this, t);
3234
+ };
3235
+ }
3236
+ function It() {
3237
+ A && (XMLHttpRequest.prototype.open = A, A = null), T && (XMLHttpRequest.prototype.send = T, T = null);
3238
+ }
3239
+ const Ht = {
3240
+ /**
3241
+ * Install network capture hooks
3242
+ */
3243
+ install(t = {}) {
3244
+ try {
3245
+ if (B)
3246
+ return;
3247
+ v = { ...Ee, ...t }, v.captureFetch && typeof globalThis.fetch == "function" && (E = globalThis.fetch, globalThis.fetch = Et()), v.captureXHR && typeof XMLHttpRequest < "u" && Tt(), B = !0, u.debug("[NetworkCapture] Installed", {
3248
+ fetch: v.captureFetch,
3249
+ xhr: v.captureXHR
3250
+ });
3251
+ } catch (e) {
3252
+ console.warn("[NetworkCapture] Install error:", e);
3253
+ }
3254
+ },
3255
+ /**
3256
+ * Uninstall network capture hooks
3257
+ */
3258
+ uninstall() {
3259
+ try {
3260
+ if (!B)
3261
+ return;
3262
+ E && (globalThis.fetch = E, E = null), It(), B = !1, u.debug("[NetworkCapture] Uninstalled");
3263
+ } catch (t) {
3264
+ console.warn("[NetworkCapture] Uninstall error:", t);
3265
+ }
3266
+ },
3267
+ /**
3268
+ * Check if capture is active
3269
+ */
3270
+ isActive() {
3271
+ return B;
3272
+ },
3273
+ /**
3274
+ * Get current configuration
3275
+ */
3276
+ getConfig() {
3277
+ return { ...v };
3278
+ },
3279
+ /**
3280
+ * Update ignore patterns
3281
+ */
3282
+ addIgnorePattern(t) {
3283
+ v.ignorePatterns.push(t);
3284
+ }
3285
+ }, Rt = {
3286
+ debug: "#6e6e6e",
3287
+ info: "#3794ff",
3288
+ warn: "#cca700",
3289
+ error: "#f14c4c"
3290
+ }, _t = {
3291
+ running: "#3794ff",
3292
+ success: "#4caf50",
3293
+ error: "#f14c4c"
3294
+ };
3295
+ class Dt {
3296
+ constructor(e) {
3297
+ p(this, "container");
3298
+ p(this, "config");
3299
+ p(this, "canvas", null);
3300
+ p(this, "ctx", null);
3301
+ p(this, "intervalId", null);
3302
+ p(this, "tooltip", null);
3303
+ // Bounds maps to avoid mutating readonly objects
3304
+ p(this, "spanBounds", /* @__PURE__ */ new Map());
3305
+ p(this, "logBounds", /* @__PURE__ */ new Map());
3306
+ const n = typeof e.container == "string" ? document.querySelector(e.container) : e.container;
3307
+ if (!n)
3308
+ throw new Error("Timeline: Container not found");
3309
+ this.container = n, this.config = {
3310
+ container: n,
3311
+ timeWindow: e.timeWindow ?? 6e4,
3312
+ refreshInterval: e.refreshInterval ?? 1e3,
3313
+ showSpans: e.showSpans ?? !0,
3314
+ showLogs: e.showLogs ?? !0,
3315
+ height: e.height ?? 200
3316
+ }, this.init();
3317
+ }
3318
+ init() {
3319
+ this.container.innerHTML = `
3320
+ <div class="devlogger-timeline" style="
3321
+ position: relative;
3322
+ background: #1e1e1e;
3323
+ border: 1px solid #3c3c3c;
3324
+ border-radius: 4px;
3325
+ overflow: hidden;
3326
+ ">
3327
+ <div class="timeline-header" style="
3328
+ display: flex;
3329
+ justify-content: space-between;
3330
+ padding: 8px 12px;
3331
+ background: #252526;
3332
+ border-bottom: 1px solid #3c3c3c;
3333
+ font-family: 'SF Mono', monospace;
3334
+ font-size: 12px;
3335
+ color: #ccc;
3336
+ ">
3337
+ <span>Timeline</span>
3338
+ <div class="timeline-controls">
3339
+ <button data-window="10000" style="
3340
+ background: transparent;
3341
+ border: 1px solid #3c3c3c;
3342
+ color: #858585;
3343
+ padding: 2px 8px;
3344
+ border-radius: 3px;
3345
+ cursor: pointer;
3346
+ margin-left: 4px;
3347
+ font-size: 11px;
3348
+ ">10s</button>
3349
+ <button data-window="30000" style="
3350
+ background: transparent;
3351
+ border: 1px solid #3c3c3c;
3352
+ color: #858585;
3353
+ padding: 2px 8px;
3354
+ border-radius: 3px;
3355
+ cursor: pointer;
3356
+ margin-left: 4px;
3357
+ font-size: 11px;
3358
+ ">30s</button>
3359
+ <button data-window="60000" style="
3360
+ background: #0e639c;
3361
+ border: 1px solid #0e639c;
3362
+ color: white;
3363
+ padding: 2px 8px;
3364
+ border-radius: 3px;
3365
+ cursor: pointer;
3366
+ margin-left: 4px;
3367
+ font-size: 11px;
3368
+ ">1m</button>
3369
+ <button data-window="300000" style="
3370
+ background: transparent;
3371
+ border: 1px solid #3c3c3c;
3372
+ color: #858585;
3373
+ padding: 2px 8px;
3374
+ border-radius: 3px;
3375
+ cursor: pointer;
3376
+ margin-left: 4px;
3377
+ font-size: 11px;
3378
+ ">5m</button>
3379
+ </div>
3380
+ </div>
3381
+ <div class="timeline-canvas-container" style="position: relative;">
3382
+ <canvas class="timeline-canvas"></canvas>
3383
+ </div>
3384
+ <div class="timeline-tooltip" style="
3385
+ position: absolute;
3386
+ display: none;
3387
+ background: #333;
3388
+ border: 1px solid #555;
3389
+ padding: 8px;
3390
+ border-radius: 4px;
3391
+ font-family: 'SF Mono', monospace;
3392
+ font-size: 11px;
3393
+ color: #ccc;
3394
+ max-width: 300px;
3395
+ z-index: 1000;
3396
+ pointer-events: none;
3397
+ "></div>
3398
+ </div>
3399
+ `, this.canvas = this.container.querySelector(".timeline-canvas"), this.tooltip = this.container.querySelector(".timeline-tooltip"), this.canvas && (this.ctx = this.canvas.getContext("2d"), this.resizeCanvas()), this.container.querySelectorAll("[data-window]").forEach((e) => {
3400
+ e.addEventListener("click", (n) => {
3401
+ const o = n.currentTarget, r = parseInt(o.dataset.window || "60000", 10);
3402
+ this.setTimeWindow(r), this.container.querySelectorAll("[data-window]").forEach((s) => {
3403
+ const c = s;
3404
+ c === o ? (c.style.background = "#0e639c", c.style.borderColor = "#0e639c", c.style.color = "white") : (c.style.background = "transparent", c.style.borderColor = "#3c3c3c", c.style.color = "#858585");
3405
+ });
3406
+ });
3407
+ }), this.canvas && (this.canvas.addEventListener("mousemove", this.handleMouseMove.bind(this)), this.canvas.addEventListener("mouseleave", this.handleMouseLeave.bind(this))), this.startRefresh();
3408
+ }
3409
+ resizeCanvas() {
3410
+ if (!this.canvas) return;
3411
+ const e = this.container.getBoundingClientRect();
3412
+ this.canvas.width = e.width - 2, this.canvas.height = this.config.height, this.render();
3413
+ }
3414
+ startRefresh() {
3415
+ this.intervalId || (this.intervalId = window.setInterval(() => this.render(), this.config.refreshInterval), this.render());
3416
+ }
3417
+ stopRefresh() {
3418
+ this.intervalId && (clearInterval(this.intervalId), this.intervalId = null);
3419
+ }
3420
+ setTimeWindow(e) {
3421
+ this.config.timeWindow = e, this.render();
3422
+ }
3423
+ render() {
3424
+ if (!this.canvas || !this.ctx) return;
3425
+ const e = this.ctx, n = this.canvas.width, o = this.canvas.height, r = Date.now(), s = r - this.config.timeWindow;
3426
+ e.fillStyle = "#1e1e1e", e.fillRect(0, 0, n, o), this.spanBounds.clear(), this.logBounds.clear(), this.drawTimeGrid(e, n, o, s, r);
3427
+ const c = u.getLogs().filter((a) => a.timestamp >= s), l = u.getSpans().filter((a) => a.startTime >= s || a.endTime && a.endTime >= s);
3428
+ this.config.showSpans && this.drawSpans(e, l, n, o, s, r), this.config.showLogs && this.drawLogs(e, c, n, o, s, r);
3429
+ }
3430
+ drawTimeGrid(e, n, o, r, s) {
3431
+ const c = s - r, l = 6;
3432
+ e.strokeStyle = "#333", e.lineWidth = 1, e.font = "10px SF Mono, monospace", e.fillStyle = "#666";
3433
+ for (let a = 0; a <= l; a++) {
3434
+ const d = a / l * n, f = r + c * a / l;
3435
+ e.beginPath(), e.moveTo(d, 0), e.lineTo(d, o), e.stroke();
3436
+ const h = new Date(f), C = `${h.getMinutes().toString().padStart(2, "0")}:${h.getSeconds().toString().padStart(2, "0")}`;
3437
+ e.fillText(C, d + 2, o - 4);
3438
+ }
3439
+ }
3440
+ drawSpans(e, n, o, r, s, c) {
3441
+ const l = c - s, a = 20, d = 4, f = 20, h = n.filter((m) => !m.parentId);
3442
+ let C = f;
3443
+ const K = (m, _, Mt = 0) => {
3444
+ const D = (m.startTime - s) / l * o, _e = ((m.endTime || c) - s) / l * o, V = Math.max(_e - D, 2), ie = _t[m.status];
3445
+ e.fillStyle = ie + "40", e.fillRect(D, _, V, a), e.strokeStyle = ie, e.lineWidth = 1, e.strokeRect(D, _, V, a), e.fillStyle = "#ccc", e.font = "10px SF Mono, monospace";
3446
+ const De = m.duration ? `${m.name} (${m.duration}ms)` : m.name;
3447
+ return e.fillText(De, D + 4, _ + 14, V - 8), this.spanBounds.set(m.id, {
3448
+ x: D,
3449
+ y: _,
3450
+ w: V,
3451
+ h: a
3452
+ }), _ + a + d;
3453
+ };
3454
+ for (const m of h)
3455
+ C = K(m, C);
3456
+ }
3457
+ drawLogs(e, n, o, r, s, c) {
3458
+ const l = c - s, a = 8, d = 30;
3459
+ for (const f of n) {
3460
+ const h = (f.timestamp - s) / l * o, C = Rt[f.level];
3461
+ e.fillStyle = C, e.beginPath(), e.moveTo(h, r - d), e.lineTo(h - a / 2, r - d - a), e.lineTo(h + a / 2, r - d - a), e.closePath(), e.fill(), this.logBounds.set(f.id, {
3462
+ x: h,
3463
+ y: r - d - a / 2,
3464
+ r: a
3465
+ });
3466
+ }
3467
+ }
3468
+ handleMouseMove(e) {
3469
+ if (!this.canvas || !this.tooltip) return;
3470
+ const n = this.canvas.getBoundingClientRect(), o = e.clientX - n.left, r = e.clientY - n.top;
3471
+ for (const s of u.getSpans()) {
3472
+ const c = this.spanBounds.get(s.id);
3473
+ if (c && o >= c.x && o <= c.x + c.w && r >= c.y && r <= c.y + c.h) {
3474
+ this.showTooltip(e, `
3475
+ <strong>${s.name}</strong><br>
3476
+ Status: ${s.status}<br>
3477
+ ${s.duration !== void 0 ? `Duration: ${s.duration}ms` : "Running..."}<br>
3478
+ ${s.context ? `Context: ${JSON.stringify(s.context)}` : ""}
3479
+ `);
3480
+ return;
3481
+ }
3482
+ }
3483
+ for (const s of u.getLogs()) {
3484
+ const c = this.logBounds.get(s.id);
3485
+ if (c && Math.sqrt((o - c.x) ** 2 + (r - c.y) ** 2) <= c.r) {
3486
+ this.showTooltip(e, `
3487
+ <strong>[${s.level.toUpperCase()}]</strong> ${s.message}<br>
3488
+ <small>${new Date(s.timestamp).toISOString()}</small>
3489
+ `);
3490
+ return;
3491
+ }
3492
+ }
3493
+ this.hideTooltip();
3494
+ }
3495
+ handleMouseLeave() {
3496
+ this.hideTooltip();
3497
+ }
3498
+ showTooltip(e, n) {
3499
+ this.tooltip && (this.tooltip.innerHTML = n, this.tooltip.style.display = "block", this.tooltip.style.left = `${e.offsetX + 10}px`, this.tooltip.style.top = `${e.offsetY + 10}px`);
3500
+ }
3501
+ hideTooltip() {
3502
+ this.tooltip && (this.tooltip.style.display = "none");
3503
+ }
3504
+ /**
3505
+ * Destroy the timeline
3506
+ */
3507
+ destroy() {
3508
+ this.stopRefresh(), this.container.innerHTML = "";
3509
+ }
3510
+ }
3511
+ function zt(t) {
3512
+ return new Dt(t);
3513
+ }
3514
+ const Nt = "0.1.0";
3515
+ export {
3516
+ j as DevLoggerUI,
3517
+ Ot as ErrorCapture,
3518
+ At as LogPersistence,
3519
+ Ht as NetworkCapture,
3520
+ Dt as Timeline,
3521
+ Nt as VERSION,
3522
+ ve as computeDiff,
3523
+ le as createDiffResult,
3524
+ zt as createTimeline,
3525
+ ae as formatValue,
3526
+ Ft as hasChanges,
3527
+ u as logger
3528
+ };
3529
+ //# sourceMappingURL=index.js.map