@junctionjs/debug 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,1200 @@
1
+ // src/store.ts
2
+ function createDebugStore(collector, maxEvents = 500) {
3
+ let entries = [];
4
+ let nextId = 1;
5
+ const listeners = /* @__PURE__ */ new Set();
6
+ const unsubscribers = [];
7
+ const counters = {
8
+ total: 0,
9
+ valid: 0,
10
+ invalid: 0,
11
+ sent: {},
12
+ errors: {},
13
+ consentChanges: 0,
14
+ queueFlushes: 0
15
+ };
16
+ function addEntry(type, payload, timestamp) {
17
+ const entry = { id: nextId++, timestamp, type, payload };
18
+ entries.push(entry);
19
+ if (entries.length > maxEvents) {
20
+ entries = entries.slice(entries.length - maxEvents);
21
+ }
22
+ switch (type) {
23
+ case "event":
24
+ counters.total++;
25
+ break;
26
+ case "event:valid":
27
+ counters.valid++;
28
+ break;
29
+ case "event:invalid":
30
+ counters.invalid++;
31
+ break;
32
+ case "destination:send": {
33
+ const dest = payload?.destination ?? "unknown";
34
+ counters.sent[dest] = (counters.sent[dest] ?? 0) + 1;
35
+ break;
36
+ }
37
+ case "destination:error": {
38
+ const errDest = payload?.destination ?? "unknown";
39
+ counters.errors[errDest] = (counters.errors[errDest] ?? 0) + 1;
40
+ break;
41
+ }
42
+ case "consent":
43
+ counters.consentChanges++;
44
+ break;
45
+ case "queue:flush":
46
+ counters.queueFlushes++;
47
+ break;
48
+ }
49
+ for (const cb of listeners) {
50
+ try {
51
+ cb();
52
+ } catch {
53
+ }
54
+ }
55
+ }
56
+ const eventTypes = [
57
+ "event",
58
+ "event:valid",
59
+ "event:invalid",
60
+ "consent",
61
+ "destination:send",
62
+ "destination:error",
63
+ "destination:init",
64
+ "queue:flush",
65
+ "error"
66
+ ];
67
+ for (const type of eventTypes) {
68
+ const handler = (data) => {
69
+ addEntry(type, data.payload, data.timestamp);
70
+ };
71
+ unsubscribers.push(collector.on(type, handler));
72
+ }
73
+ return {
74
+ getEntries() {
75
+ return [...entries];
76
+ },
77
+ getByType(type) {
78
+ return entries.filter((e) => e.type === type);
79
+ },
80
+ getCounters() {
81
+ return { ...counters, sent: { ...counters.sent }, errors: { ...counters.errors } };
82
+ },
83
+ clear() {
84
+ entries = [];
85
+ counters.total = 0;
86
+ counters.valid = 0;
87
+ counters.invalid = 0;
88
+ counters.sent = {};
89
+ counters.errors = {};
90
+ counters.consentChanges = 0;
91
+ counters.queueFlushes = 0;
92
+ for (const cb of listeners) {
93
+ try {
94
+ cb();
95
+ } catch {
96
+ }
97
+ }
98
+ },
99
+ onUpdate(callback) {
100
+ listeners.add(callback);
101
+ return () => {
102
+ listeners.delete(callback);
103
+ };
104
+ },
105
+ destroy() {
106
+ for (const unsub of unsubscribers) {
107
+ unsub();
108
+ }
109
+ unsubscribers.length = 0;
110
+ listeners.clear();
111
+ }
112
+ };
113
+ }
114
+
115
+ // src/styles.ts
116
+ var PANEL_STYLES = (
117
+ /* css */
118
+ `
119
+ :host {
120
+ --jd-bg: #1a1a2e;
121
+ --jd-bg-surface: #16213e;
122
+ --jd-bg-hover: #1f2f50;
123
+ --jd-bg-active: #0f3460;
124
+ --jd-border: #2a2a4a;
125
+ --jd-text: #e0e0e0;
126
+ --jd-text-dim: #888;
127
+ --jd-text-bright: #fff;
128
+ --jd-accent: #e94560;
129
+ --jd-green: #4ade80;
130
+ --jd-yellow: #fbbf24;
131
+ --jd-red: #f87171;
132
+ --jd-blue: #60a5fa;
133
+ --jd-purple: #a78bfa;
134
+ --jd-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
135
+ --jd-mono: "SF Mono", "Fira Code", "Cascadia Code", Consolas, monospace;
136
+ --jd-radius: 6px;
137
+
138
+ all: initial;
139
+ font-family: var(--jd-font);
140
+ font-size: 12px;
141
+ color: var(--jd-text);
142
+ line-height: 1.4;
143
+ }
144
+
145
+ /* \u2500\u2500\u2500 FAB (collapsed state) \u2500\u2500\u2500 */
146
+
147
+ .jd-fab {
148
+ position: fixed;
149
+ z-index: 2147483646;
150
+ width: 36px;
151
+ height: 36px;
152
+ border-radius: 50%;
153
+ background: var(--jd-accent);
154
+ color: #fff;
155
+ border: none;
156
+ cursor: pointer;
157
+ display: flex;
158
+ align-items: center;
159
+ justify-content: center;
160
+ font-weight: 700;
161
+ font-size: 16px;
162
+ font-family: var(--jd-mono);
163
+ box-shadow: 0 2px 12px rgba(0,0,0,0.4);
164
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
165
+ user-select: none;
166
+ }
167
+
168
+ .jd-fab:hover {
169
+ transform: scale(1.1);
170
+ box-shadow: 0 4px 20px rgba(233,69,96,0.4);
171
+ }
172
+
173
+ .jd-fab .jd-fab-badge {
174
+ position: absolute;
175
+ top: -4px;
176
+ right: -4px;
177
+ min-width: 16px;
178
+ height: 16px;
179
+ padding: 0 4px;
180
+ border-radius: 8px;
181
+ background: var(--jd-green);
182
+ color: #000;
183
+ font-size: 9px;
184
+ font-weight: 700;
185
+ display: flex;
186
+ align-items: center;
187
+ justify-content: center;
188
+ }
189
+
190
+ /* \u2500\u2500\u2500 Position variants \u2500\u2500\u2500 */
191
+
192
+ .jd-pos-bottom-right { bottom: 16px; right: 16px; }
193
+ .jd-pos-bottom-left { bottom: 16px; left: 16px; }
194
+ .jd-pos-top-right { top: 16px; right: 16px; }
195
+ .jd-pos-top-left { top: 16px; left: 16px; }
196
+
197
+ .jd-panel.jd-pos-bottom-right { bottom: 60px; right: 16px; }
198
+ .jd-panel.jd-pos-bottom-left { bottom: 60px; left: 16px; }
199
+ .jd-panel.jd-pos-top-right { top: 60px; right: 16px; }
200
+ .jd-panel.jd-pos-top-left { top: 60px; left: 16px; }
201
+
202
+ /* \u2500\u2500\u2500 Panel (expanded state) \u2500\u2500\u2500 */
203
+
204
+ .jd-panel {
205
+ position: fixed;
206
+ z-index: 2147483646;
207
+ width: 420px;
208
+ height: 360px;
209
+ background: var(--jd-bg);
210
+ border: 1px solid var(--jd-border);
211
+ border-radius: var(--jd-radius);
212
+ box-shadow: 0 8px 32px rgba(0,0,0,0.5);
213
+ display: none;
214
+ flex-direction: column;
215
+ overflow: hidden;
216
+ }
217
+
218
+ .jd-panel.jd-open {
219
+ display: flex;
220
+ }
221
+
222
+ /* \u2500\u2500\u2500 Header \u2500\u2500\u2500 */
223
+
224
+ .jd-header {
225
+ display: flex;
226
+ align-items: center;
227
+ justify-content: space-between;
228
+ padding: 6px 10px;
229
+ background: var(--jd-bg-surface);
230
+ border-bottom: 1px solid var(--jd-border);
231
+ cursor: default;
232
+ user-select: none;
233
+ }
234
+
235
+ .jd-header-title {
236
+ font-weight: 600;
237
+ font-size: 11px;
238
+ color: var(--jd-text-bright);
239
+ letter-spacing: 0.5px;
240
+ text-transform: uppercase;
241
+ }
242
+
243
+ .jd-header-title span {
244
+ color: var(--jd-accent);
245
+ }
246
+
247
+ .jd-header-actions {
248
+ display: flex;
249
+ gap: 6px;
250
+ }
251
+
252
+ .jd-header-btn {
253
+ background: none;
254
+ border: none;
255
+ color: var(--jd-text-dim);
256
+ cursor: pointer;
257
+ padding: 2px 4px;
258
+ font-size: 12px;
259
+ border-radius: 3px;
260
+ }
261
+
262
+ .jd-header-btn:hover {
263
+ color: var(--jd-text-bright);
264
+ background: var(--jd-bg-hover);
265
+ }
266
+
267
+ /* \u2500\u2500\u2500 Tabs \u2500\u2500\u2500 */
268
+
269
+ .jd-tabs {
270
+ display: flex;
271
+ gap: 0;
272
+ background: var(--jd-bg-surface);
273
+ border-bottom: 1px solid var(--jd-border);
274
+ }
275
+
276
+ .jd-tab {
277
+ flex: 1;
278
+ padding: 6px 8px;
279
+ background: none;
280
+ border: none;
281
+ border-bottom: 2px solid transparent;
282
+ color: var(--jd-text-dim);
283
+ cursor: pointer;
284
+ font-size: 11px;
285
+ font-family: var(--jd-font);
286
+ transition: color 0.1s, border-color 0.1s;
287
+ text-align: center;
288
+ }
289
+
290
+ .jd-tab:hover {
291
+ color: var(--jd-text);
292
+ }
293
+
294
+ .jd-tab.jd-active {
295
+ color: var(--jd-accent);
296
+ border-bottom-color: var(--jd-accent);
297
+ }
298
+
299
+ .jd-tab-badge {
300
+ display: inline-block;
301
+ min-width: 14px;
302
+ height: 14px;
303
+ padding: 0 3px;
304
+ margin-left: 4px;
305
+ border-radius: 7px;
306
+ background: var(--jd-bg-hover);
307
+ font-size: 9px;
308
+ line-height: 14px;
309
+ text-align: center;
310
+ vertical-align: middle;
311
+ }
312
+
313
+ /* \u2500\u2500\u2500 Tab content area \u2500\u2500\u2500 */
314
+
315
+ .jd-content {
316
+ flex: 1;
317
+ overflow: hidden;
318
+ position: relative;
319
+ }
320
+
321
+ .jd-tab-panel {
322
+ display: none;
323
+ position: absolute;
324
+ inset: 0;
325
+ overflow-y: auto;
326
+ padding: 0;
327
+ }
328
+
329
+ .jd-tab-panel.jd-active {
330
+ display: block;
331
+ }
332
+
333
+ .jd-tab-panel::-webkit-scrollbar { width: 6px; }
334
+ .jd-tab-panel::-webkit-scrollbar-track { background: transparent; }
335
+ .jd-tab-panel::-webkit-scrollbar-thumb { background: var(--jd-border); border-radius: 3px; }
336
+
337
+ /* \u2500\u2500\u2500 Filter bar \u2500\u2500\u2500 */
338
+
339
+ .jd-filter {
340
+ display: flex;
341
+ align-items: center;
342
+ gap: 6px;
343
+ padding: 6px 8px;
344
+ border-bottom: 1px solid var(--jd-border);
345
+ background: var(--jd-bg-surface);
346
+ }
347
+
348
+ .jd-filter input {
349
+ flex: 1;
350
+ background: var(--jd-bg);
351
+ border: 1px solid var(--jd-border);
352
+ border-radius: 3px;
353
+ color: var(--jd-text);
354
+ padding: 3px 6px;
355
+ font-size: 11px;
356
+ font-family: var(--jd-mono);
357
+ outline: none;
358
+ }
359
+
360
+ .jd-filter input:focus {
361
+ border-color: var(--jd-accent);
362
+ }
363
+
364
+ .jd-filter input::placeholder {
365
+ color: var(--jd-text-dim);
366
+ }
367
+
368
+ /* \u2500\u2500\u2500 Event list \u2500\u2500\u2500 */
369
+
370
+ .jd-event-list {
371
+ list-style: none;
372
+ margin: 0;
373
+ padding: 0;
374
+ }
375
+
376
+ .jd-event-row {
377
+ display: flex;
378
+ align-items: center;
379
+ gap: 6px;
380
+ padding: 5px 8px;
381
+ border-bottom: 1px solid var(--jd-border);
382
+ cursor: pointer;
383
+ transition: background 0.1s;
384
+ font-size: 11px;
385
+ }
386
+
387
+ .jd-event-row:hover {
388
+ background: var(--jd-bg-hover);
389
+ }
390
+
391
+ .jd-event-time {
392
+ color: var(--jd-text-dim);
393
+ font-family: var(--jd-mono);
394
+ font-size: 10px;
395
+ flex-shrink: 0;
396
+ width: 55px;
397
+ }
398
+
399
+ .jd-event-badge {
400
+ display: inline-block;
401
+ padding: 1px 6px;
402
+ border-radius: 3px;
403
+ font-family: var(--jd-mono);
404
+ font-size: 10px;
405
+ font-weight: 600;
406
+ white-space: nowrap;
407
+ }
408
+
409
+ .jd-badge-event { background: #1e3a5f; color: var(--jd-blue); }
410
+ .jd-badge-valid { background: #1a3d2e; color: var(--jd-green); }
411
+ .jd-badge-invalid { background: #3d1a1a; color: var(--jd-red); }
412
+ .jd-badge-send { background: #1e3a5f; color: var(--jd-blue); }
413
+ .jd-badge-error { background: #3d1a1a; color: var(--jd-red); }
414
+ .jd-badge-consent { background: #2e1a3d; color: var(--jd-purple); }
415
+ .jd-badge-init { background: #1a3d2e; color: var(--jd-green); }
416
+ .jd-badge-queue { background: #3d3a1a; color: var(--jd-yellow); }
417
+
418
+ .jd-event-name {
419
+ flex: 1;
420
+ overflow: hidden;
421
+ text-overflow: ellipsis;
422
+ white-space: nowrap;
423
+ font-family: var(--jd-mono);
424
+ }
425
+
426
+ .jd-event-dest {
427
+ color: var(--jd-text-dim);
428
+ font-size: 10px;
429
+ flex-shrink: 0;
430
+ }
431
+
432
+ /* \u2500\u2500\u2500 Event detail (expanded) \u2500\u2500\u2500 */
433
+
434
+ .jd-event-detail {
435
+ display: none;
436
+ padding: 6px 8px 8px 8px;
437
+ background: var(--jd-bg-surface);
438
+ border-bottom: 1px solid var(--jd-border);
439
+ font-family: var(--jd-mono);
440
+ font-size: 10px;
441
+ white-space: pre-wrap;
442
+ word-break: break-all;
443
+ color: var(--jd-text);
444
+ max-height: 200px;
445
+ overflow-y: auto;
446
+ }
447
+
448
+ .jd-event-detail.jd-expanded {
449
+ display: block;
450
+ }
451
+
452
+ .jd-detail-key {
453
+ color: var(--jd-purple);
454
+ }
455
+
456
+ .jd-detail-string {
457
+ color: var(--jd-green);
458
+ }
459
+
460
+ .jd-detail-number {
461
+ color: var(--jd-yellow);
462
+ }
463
+
464
+ .jd-detail-bool {
465
+ color: var(--jd-blue);
466
+ }
467
+
468
+ .jd-detail-null {
469
+ color: var(--jd-text-dim);
470
+ }
471
+
472
+ /* \u2500\u2500\u2500 Consent tab \u2500\u2500\u2500 */
473
+
474
+ .jd-consent-grid {
475
+ display: grid;
476
+ grid-template-columns: 1fr 1fr;
477
+ gap: 6px;
478
+ padding: 8px;
479
+ }
480
+
481
+ .jd-consent-card {
482
+ background: var(--jd-bg-surface);
483
+ border: 1px solid var(--jd-border);
484
+ border-radius: var(--jd-radius);
485
+ padding: 8px 10px;
486
+ display: flex;
487
+ align-items: center;
488
+ justify-content: space-between;
489
+ }
490
+
491
+ .jd-consent-label {
492
+ font-size: 11px;
493
+ font-weight: 600;
494
+ text-transform: capitalize;
495
+ }
496
+
497
+ .jd-consent-status {
498
+ font-size: 14px;
499
+ cursor: pointer;
500
+ user-select: none;
501
+ }
502
+
503
+ .jd-consent-granted { color: var(--jd-green); }
504
+ .jd-consent-denied { color: var(--jd-red); }
505
+ .jd-consent-pending { color: var(--jd-yellow); }
506
+
507
+ .jd-consent-info {
508
+ padding: 8px;
509
+ font-size: 11px;
510
+ color: var(--jd-text-dim);
511
+ text-align: center;
512
+ border-top: 1px solid var(--jd-border);
513
+ }
514
+
515
+ /* \u2500\u2500\u2500 Destinations tab \u2500\u2500\u2500 */
516
+
517
+ .jd-dest-list {
518
+ list-style: none;
519
+ margin: 0;
520
+ padding: 0;
521
+ }
522
+
523
+ .jd-dest-row {
524
+ display: flex;
525
+ align-items: center;
526
+ gap: 8px;
527
+ padding: 8px 10px;
528
+ border-bottom: 1px solid var(--jd-border);
529
+ }
530
+
531
+ .jd-dest-status {
532
+ width: 8px;
533
+ height: 8px;
534
+ border-radius: 50%;
535
+ flex-shrink: 0;
536
+ }
537
+
538
+ .jd-dest-ok { background: var(--jd-green); }
539
+ .jd-dest-err { background: var(--jd-red); }
540
+ .jd-dest-pending { background: var(--jd-yellow); }
541
+
542
+ .jd-dest-info {
543
+ flex: 1;
544
+ }
545
+
546
+ .jd-dest-name {
547
+ font-weight: 600;
548
+ font-size: 11px;
549
+ color: var(--jd-text-bright);
550
+ }
551
+
552
+ .jd-dest-meta {
553
+ font-size: 10px;
554
+ color: var(--jd-text-dim);
555
+ font-family: var(--jd-mono);
556
+ }
557
+
558
+ .jd-dest-count {
559
+ font-family: var(--jd-mono);
560
+ font-size: 11px;
561
+ color: var(--jd-text-dim);
562
+ text-align: right;
563
+ flex-shrink: 0;
564
+ }
565
+
566
+ .jd-dest-count strong {
567
+ color: var(--jd-text-bright);
568
+ }
569
+
570
+ /* \u2500\u2500\u2500 Context tab \u2500\u2500\u2500 */
571
+
572
+ .jd-context-section {
573
+ padding: 6px 8px;
574
+ border-bottom: 1px solid var(--jd-border);
575
+ }
576
+
577
+ .jd-context-heading {
578
+ font-size: 10px;
579
+ font-weight: 600;
580
+ text-transform: uppercase;
581
+ letter-spacing: 0.5px;
582
+ color: var(--jd-text-dim);
583
+ margin-bottom: 4px;
584
+ }
585
+
586
+ .jd-context-row {
587
+ display: flex;
588
+ justify-content: space-between;
589
+ padding: 2px 0;
590
+ font-size: 11px;
591
+ }
592
+
593
+ .jd-context-key {
594
+ color: var(--jd-text-dim);
595
+ }
596
+
597
+ .jd-context-val {
598
+ color: var(--jd-text-bright);
599
+ font-family: var(--jd-mono);
600
+ text-align: right;
601
+ max-width: 240px;
602
+ overflow: hidden;
603
+ text-overflow: ellipsis;
604
+ white-space: nowrap;
605
+ }
606
+
607
+ /* \u2500\u2500\u2500 Empty state \u2500\u2500\u2500 */
608
+
609
+ .jd-empty {
610
+ display: flex;
611
+ align-items: center;
612
+ justify-content: center;
613
+ height: 100%;
614
+ color: var(--jd-text-dim);
615
+ font-size: 12px;
616
+ padding: 20px;
617
+ text-align: center;
618
+ }
619
+
620
+ /* \u2500\u2500\u2500 Status bar \u2500\u2500\u2500 */
621
+
622
+ .jd-statusbar {
623
+ display: flex;
624
+ align-items: center;
625
+ justify-content: space-between;
626
+ padding: 4px 8px;
627
+ background: var(--jd-bg-surface);
628
+ border-top: 1px solid var(--jd-border);
629
+ font-size: 10px;
630
+ color: var(--jd-text-dim);
631
+ user-select: none;
632
+ }
633
+
634
+ .jd-statusbar-counters {
635
+ display: flex;
636
+ gap: 10px;
637
+ }
638
+
639
+ .jd-statusbar-counter {
640
+ display: flex;
641
+ align-items: center;
642
+ gap: 3px;
643
+ }
644
+
645
+ .jd-dot {
646
+ width: 6px;
647
+ height: 6px;
648
+ border-radius: 50%;
649
+ display: inline-block;
650
+ }
651
+
652
+ .jd-dot-green { background: var(--jd-green); }
653
+ .jd-dot-red { background: var(--jd-red); }
654
+ .jd-dot-yellow { background: var(--jd-yellow); }
655
+ .jd-dot-blue { background: var(--jd-blue); }
656
+ `
657
+ );
658
+
659
+ // src/panel.ts
660
+ function el(tag, attrs, ...children) {
661
+ const elem = document.createElement(tag);
662
+ if (attrs) {
663
+ for (const [k, v] of Object.entries(attrs)) {
664
+ if (k === "class") elem.className = v;
665
+ else elem.setAttribute(k, v);
666
+ }
667
+ }
668
+ for (const child of children) {
669
+ if (typeof child === "string") elem.appendChild(document.createTextNode(child));
670
+ else elem.appendChild(child);
671
+ }
672
+ return elem;
673
+ }
674
+ function formatTime(iso) {
675
+ try {
676
+ const d = new Date(iso);
677
+ return d.toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" });
678
+ } catch {
679
+ return "??:??:??";
680
+ }
681
+ }
682
+ function badgeClass(type) {
683
+ const map = {
684
+ "event": "jd-badge-event",
685
+ "event:valid": "jd-badge-valid",
686
+ "event:invalid": "jd-badge-invalid",
687
+ "destination:send": "jd-badge-send",
688
+ "destination:error": "jd-badge-error",
689
+ "destination:init": "jd-badge-init",
690
+ "consent": "jd-badge-consent",
691
+ "queue:flush": "jd-badge-queue",
692
+ "error": "jd-badge-error"
693
+ };
694
+ return map[type] ?? "jd-badge-event";
695
+ }
696
+ function eventLabel(entry) {
697
+ const p = entry.payload;
698
+ if (!p) return entry.type;
699
+ if ("entity" in p && "action" in p) {
700
+ return `${p.entity}:${p.action}`;
701
+ }
702
+ if ("event" in p) {
703
+ const evt = p.event;
704
+ if (evt?.entity && evt?.action) return `${evt.entity}:${evt.action}`;
705
+ }
706
+ if ("destination" in p) {
707
+ return `${p.destination}`;
708
+ }
709
+ if ("state" in p) return "state updated";
710
+ if ("count" in p) return `${p.count} events`;
711
+ return entry.type;
712
+ }
713
+ function destLabel(entry) {
714
+ const p = entry.payload;
715
+ if (!p) return "";
716
+ if ("destination" in p && typeof p.destination === "string") return p.destination;
717
+ return "";
718
+ }
719
+ function syntaxHighlight(obj, indent = 0) {
720
+ if (obj === null || obj === void 0) {
721
+ return `<span class="jd-detail-null">${String(obj)}</span>`;
722
+ }
723
+ if (typeof obj === "string") {
724
+ const escaped = obj.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
725
+ return `<span class="jd-detail-string">"${escaped}"</span>`;
726
+ }
727
+ if (typeof obj === "number") {
728
+ return `<span class="jd-detail-number">${obj}</span>`;
729
+ }
730
+ if (typeof obj === "boolean") {
731
+ return `<span class="jd-detail-bool">${obj}</span>`;
732
+ }
733
+ if (Array.isArray(obj)) {
734
+ if (obj.length === 0) return "[]";
735
+ const pad = " ".repeat(indent + 1);
736
+ const closePad = " ".repeat(indent);
737
+ const items = obj.map((v) => `${pad}${syntaxHighlight(v, indent + 1)}`).join(",\n");
738
+ return `[
739
+ ${items}
740
+ ${closePad}]`;
741
+ }
742
+ if (typeof obj === "object") {
743
+ const entries = Object.entries(obj);
744
+ if (entries.length === 0) return "{}";
745
+ const pad = " ".repeat(indent + 1);
746
+ const closePad = " ".repeat(indent);
747
+ const items = entries.map(([k, v]) => `${pad}<span class="jd-detail-key">${k}</span>: ${syntaxHighlight(v, indent + 1)}`).join(",\n");
748
+ return `{
749
+ ${items}
750
+ ${closePad}}`;
751
+ }
752
+ return String(obj);
753
+ }
754
+ function createPanel(collector, store, options) {
755
+ const posClass = `jd-pos-${options.position}`;
756
+ const host = document.createElement("junction-debug");
757
+ const shadow = host.attachShadow({ mode: "open" });
758
+ const style = document.createElement("style");
759
+ style.textContent = PANEL_STYLES;
760
+ shadow.appendChild(style);
761
+ let isOpen = options.startOpen;
762
+ let activeTab = "events";
763
+ let filterText = "";
764
+ let expandedEventId = null;
765
+ const fab = el("button", { class: `jd-fab ${posClass}` }, "J");
766
+ const fabBadge = el("span", { class: "jd-fab-badge" }, "0");
767
+ fab.appendChild(fabBadge);
768
+ fab.addEventListener("click", toggle);
769
+ shadow.appendChild(fab);
770
+ const panel = el("div", { class: `jd-panel ${posClass} ${isOpen ? "jd-open" : ""}` });
771
+ shadow.appendChild(panel);
772
+ const clearBtn = el("button", { class: "jd-header-btn", title: "Clear" }, "\u2715 Clear");
773
+ clearBtn.addEventListener("click", () => {
774
+ store.clear();
775
+ render();
776
+ });
777
+ const closeBtn = el("button", { class: "jd-header-btn", title: "Close" }, "\u2715");
778
+ closeBtn.addEventListener("click", close);
779
+ const header = el(
780
+ "div",
781
+ { class: "jd-header" },
782
+ el(
783
+ "div",
784
+ { class: "jd-header-title" },
785
+ el("span", {}, "J"),
786
+ " unction Debug"
787
+ ),
788
+ el("div", { class: "jd-header-actions" }, clearBtn, closeBtn)
789
+ );
790
+ panel.appendChild(header);
791
+ const tabDefs = [
792
+ { id: "events", label: "Events" },
793
+ { id: "consent", label: "Consent" },
794
+ { id: "destinations", label: "Dests" },
795
+ { id: "context", label: "Context" }
796
+ ];
797
+ const tabBar = el("div", { class: "jd-tabs" });
798
+ const tabBtns = {};
799
+ for (const td of tabDefs) {
800
+ const btn = el("button", { class: `jd-tab ${td.id === activeTab ? "jd-active" : ""}` }, td.label);
801
+ btn.addEventListener("click", () => {
802
+ switchTab(td.id);
803
+ });
804
+ tabBtns[td.id] = btn;
805
+ tabBar.appendChild(btn);
806
+ }
807
+ panel.appendChild(tabBar);
808
+ const content = el("div", { class: "jd-content" });
809
+ panel.appendChild(content);
810
+ const tabPanels = {};
811
+ for (const td of tabDefs) {
812
+ const tp = el("div", { class: `jd-tab-panel ${td.id === activeTab ? "jd-active" : ""}` });
813
+ tp.dataset.tab = td.id;
814
+ tabPanels[td.id] = tp;
815
+ content.appendChild(tp);
816
+ }
817
+ const statusbar = el("div", { class: "jd-statusbar" });
818
+ panel.appendChild(statusbar);
819
+ function switchTab(id) {
820
+ activeTab = id;
821
+ for (const [key, btn] of Object.entries(tabBtns)) {
822
+ btn.classList.toggle("jd-active", key === id);
823
+ }
824
+ for (const [key, tp] of Object.entries(tabPanels)) {
825
+ tp.classList.toggle("jd-active", key === id);
826
+ }
827
+ render();
828
+ }
829
+ function open() {
830
+ isOpen = true;
831
+ panel.classList.add("jd-open");
832
+ fab.style.display = "none";
833
+ render();
834
+ }
835
+ function close() {
836
+ isOpen = false;
837
+ panel.classList.remove("jd-open");
838
+ fab.style.display = "flex";
839
+ }
840
+ function toggle() {
841
+ if (isOpen) close();
842
+ else open();
843
+ }
844
+ function render() {
845
+ renderFab();
846
+ renderStatusBar();
847
+ if (!isOpen) return;
848
+ switch (activeTab) {
849
+ case "events":
850
+ renderEvents();
851
+ break;
852
+ case "consent":
853
+ renderConsent();
854
+ break;
855
+ case "destinations":
856
+ renderDestinations();
857
+ break;
858
+ case "context":
859
+ renderContext();
860
+ break;
861
+ }
862
+ }
863
+ function renderFab() {
864
+ const c = store.getCounters();
865
+ fabBadge.textContent = String(c.total);
866
+ fabBadge.style.display = c.total > 0 ? "flex" : "none";
867
+ }
868
+ function renderStatusBar() {
869
+ const c = store.getCounters();
870
+ statusbar.innerHTML = "";
871
+ const counters = el("div", { class: "jd-statusbar-counters" });
872
+ const addCounter = (dotClass, label, count) => {
873
+ const item = el("span", { class: "jd-statusbar-counter" });
874
+ item.appendChild(el("span", { class: `jd-dot ${dotClass}` }));
875
+ item.appendChild(document.createTextNode(` ${count} ${label}`));
876
+ counters.appendChild(item);
877
+ };
878
+ addCounter("jd-dot-green", "valid", c.valid);
879
+ addCounter("jd-dot-red", "invalid", c.invalid);
880
+ addCounter("jd-dot-blue", "sent", Object.values(c.sent).reduce((a, b) => a + b, 0));
881
+ statusbar.appendChild(counters);
882
+ statusbar.appendChild(document.createTextNode(`${store.getEntries().length} entries`));
883
+ }
884
+ function renderEvents() {
885
+ const tp = tabPanels.events;
886
+ tp.innerHTML = "";
887
+ const filter = el("div", { class: "jd-filter" });
888
+ const input = el("input", { type: "text", placeholder: "Filter events..." });
889
+ input.value = filterText;
890
+ input.addEventListener("input", () => {
891
+ filterText = input.value;
892
+ renderEventList(list);
893
+ });
894
+ filter.appendChild(input);
895
+ tp.appendChild(filter);
896
+ const list = el("ul", { class: "jd-event-list" });
897
+ tp.appendChild(list);
898
+ renderEventList(list);
899
+ }
900
+ function renderEventList(list) {
901
+ list.innerHTML = "";
902
+ let entries = store.getEntries();
903
+ if (filterText) {
904
+ const lower = filterText.toLowerCase();
905
+ entries = entries.filter((e) => {
906
+ const label = eventLabel(e).toLowerCase();
907
+ const type = e.type.toLowerCase();
908
+ return label.includes(lower) || type.includes(lower);
909
+ });
910
+ }
911
+ if (entries.length === 0) {
912
+ list.appendChild(el("div", { class: "jd-empty" }, filterText ? "No matching events" : "Waiting for events\u2026"));
913
+ return;
914
+ }
915
+ for (let i = entries.length - 1; i >= 0; i--) {
916
+ const entry = entries[i];
917
+ const row = el("li", { class: "jd-event-row" });
918
+ row.appendChild(el("span", { class: "jd-event-time" }, formatTime(entry.timestamp)));
919
+ row.appendChild(el("span", { class: `jd-event-badge ${badgeClass(entry.type)}` }, entry.type.replace("destination:", "dest:")));
920
+ row.appendChild(el("span", { class: "jd-event-name" }, eventLabel(entry)));
921
+ const dl = destLabel(entry);
922
+ if (dl) {
923
+ row.appendChild(el("span", { class: "jd-event-dest" }, dl));
924
+ }
925
+ const detail = el("div", { class: "jd-event-detail" });
926
+ detail.innerHTML = syntaxHighlight(entry.payload);
927
+ row.addEventListener("click", () => {
928
+ if (expandedEventId === entry.id) {
929
+ expandedEventId = null;
930
+ detail.classList.remove("jd-expanded");
931
+ } else {
932
+ const prev = list.querySelector(".jd-event-detail.jd-expanded");
933
+ if (prev) prev.classList.remove("jd-expanded");
934
+ expandedEventId = entry.id;
935
+ detail.classList.add("jd-expanded");
936
+ }
937
+ });
938
+ list.appendChild(row);
939
+ list.appendChild(detail);
940
+ }
941
+ }
942
+ function renderConsent() {
943
+ const tp = tabPanels.consent;
944
+ tp.innerHTML = "";
945
+ const state = collector.getConsent();
946
+ const categories = ["necessary", "analytics", "marketing", "personalization", "social"];
947
+ const grid = el("div", { class: "jd-consent-grid" });
948
+ for (const cat of categories) {
949
+ const value = state[cat];
950
+ const card = el("div", { class: "jd-consent-card" });
951
+ card.appendChild(el("span", { class: "jd-consent-label" }, cat));
952
+ let statusText;
953
+ let statusClass;
954
+ if (value === true) {
955
+ statusText = "\u2713";
956
+ statusClass = "jd-consent-granted";
957
+ } else if (value === false) {
958
+ statusText = "\u2717";
959
+ statusClass = "jd-consent-denied";
960
+ } else {
961
+ statusText = "\u25CB";
962
+ statusClass = "jd-consent-pending";
963
+ }
964
+ const statusEl = el("span", { class: `jd-consent-status ${statusClass}`, title: "Click to toggle (testing)" }, statusText);
965
+ if (cat !== "necessary") {
966
+ statusEl.addEventListener("click", () => {
967
+ const current = collector.getConsent()[cat];
968
+ const next = current === true ? false : true;
969
+ collector.consent({ [cat]: next });
970
+ requestAnimationFrame(() => renderConsent());
971
+ });
972
+ }
973
+ card.appendChild(statusEl);
974
+ grid.appendChild(card);
975
+ }
976
+ tp.appendChild(grid);
977
+ const counters = store.getCounters();
978
+ tp.appendChild(el(
979
+ "div",
980
+ { class: "jd-consent-info" },
981
+ `${counters.consentChanges} consent changes \xB7 ${counters.queueFlushes} queue flushes \xB7 Click status to toggle (testing only)`
982
+ ));
983
+ }
984
+ function renderDestinations() {
985
+ const tp = tabPanels.destinations;
986
+ tp.innerHTML = "";
987
+ const counters = store.getCounters();
988
+ const initEntries = store.getByType("destination:init");
989
+ const errorEntries = store.getByType("destination:error");
990
+ const initNames = new Set(
991
+ initEntries.map((e) => e.payload?.destination).filter(Boolean)
992
+ );
993
+ const errorNames = new Set(
994
+ errorEntries.map((e) => e.payload?.destination).filter(Boolean)
995
+ );
996
+ const allNames = /* @__PURE__ */ new Set([
997
+ ...Object.keys(counters.sent),
998
+ ...Object.keys(counters.errors),
999
+ ...initNames,
1000
+ ...errorNames
1001
+ ]);
1002
+ if (allNames.size === 0) {
1003
+ tp.appendChild(el("div", { class: "jd-empty" }, "No destinations registered yet"));
1004
+ return;
1005
+ }
1006
+ const list = el("ul", { class: "jd-dest-list" });
1007
+ for (const name of allNames) {
1008
+ const row = el("li", { class: "jd-dest-row" });
1009
+ const hasError = errorNames.has(name) && !initNames.has(name);
1010
+ const isInit = initNames.has(name);
1011
+ const statusClass = hasError ? "jd-dest-err" : isInit ? "jd-dest-ok" : "jd-dest-pending";
1012
+ row.appendChild(el("span", { class: `jd-dest-status ${statusClass}` }));
1013
+ const info = el("div", { class: "jd-dest-info" });
1014
+ info.appendChild(el("div", { class: "jd-dest-name" }, name));
1015
+ const statusLabel = hasError ? "error" : isInit ? "ready" : "initializing";
1016
+ info.appendChild(el("div", { class: "jd-dest-meta" }, statusLabel));
1017
+ row.appendChild(info);
1018
+ const sentCount = counters.sent[name] ?? 0;
1019
+ const errCount = counters.errors[name] ?? 0;
1020
+ const countEl = el("div", { class: "jd-dest-count" });
1021
+ countEl.innerHTML = `<strong>${sentCount}</strong> sent${errCount > 0 ? ` \xB7 <span style="color:var(--jd-red)">${errCount} err</span>` : ""}`;
1022
+ row.appendChild(countEl);
1023
+ list.appendChild(row);
1024
+ }
1025
+ tp.appendChild(list);
1026
+ }
1027
+ function renderContext() {
1028
+ const tp = tabPanels.context;
1029
+ tp.innerHTML = "";
1030
+ const entries = store.getEntries();
1031
+ const latestEvent = [...entries].reverse().find(
1032
+ (e) => e.type === "event:valid" || e.type === "event"
1033
+ );
1034
+ const event = latestEvent?.payload;
1035
+ const addSection = (title, rows) => {
1036
+ const section = el("div", { class: "jd-context-section" });
1037
+ section.appendChild(el("div", { class: "jd-context-heading" }, title));
1038
+ for (const [key, val] of rows) {
1039
+ if (val === void 0 || val === "") continue;
1040
+ const row = el("div", { class: "jd-context-row" });
1041
+ row.appendChild(el("span", { class: "jd-context-key" }, key));
1042
+ row.appendChild(el("span", { class: "jd-context-val" }, val));
1043
+ section.appendChild(row);
1044
+ }
1045
+ tp.appendChild(section);
1046
+ };
1047
+ if (event?.context?.page) {
1048
+ const p = event.context.page;
1049
+ addSection("Page", [
1050
+ ["URL", p.url],
1051
+ ["Path", p.path],
1052
+ ["Title", p.title],
1053
+ ["Referrer", p.referrer]
1054
+ ]);
1055
+ } else {
1056
+ addSection("Page", [
1057
+ ["URL", window.location.href],
1058
+ ["Path", window.location.pathname],
1059
+ ["Title", document.title],
1060
+ ["Referrer", document.referrer]
1061
+ ]);
1062
+ }
1063
+ if (event?.context?.device) {
1064
+ const d = event.context.device;
1065
+ addSection("Device", [
1066
+ ["Type", d.type],
1067
+ ["Language", d.language],
1068
+ ["Viewport", d.viewport ? `${d.viewport.width}\xD7${d.viewport.height}` : void 0],
1069
+ ["Screen", d.screenResolution ? `${d.screenResolution.width}\xD7${d.screenResolution.height}` : void 0]
1070
+ ]);
1071
+ }
1072
+ if (event?.context?.campaign) {
1073
+ const c = event.context.campaign;
1074
+ addSection("Campaign", [
1075
+ ["Source", c.source],
1076
+ ["Medium", c.medium],
1077
+ ["Campaign", c.campaign],
1078
+ ["Term", c.term],
1079
+ ["Content", c.content]
1080
+ ]);
1081
+ }
1082
+ if (event?.user) {
1083
+ addSection("User", [
1084
+ ["Anonymous ID", event.user.anonymousId],
1085
+ ["User ID", event.user.userId],
1086
+ ...event.user.traits ? Object.entries(event.user.traits).map(([k, v]) => [k, String(v)]) : []
1087
+ ]);
1088
+ }
1089
+ if (event?.context?.session) {
1090
+ const s = event.context.session;
1091
+ addSection("Session", [
1092
+ ["ID", s.id],
1093
+ ["New", String(s.isNew)],
1094
+ ["Count", String(s.count)]
1095
+ ]);
1096
+ }
1097
+ const consent = collector.getConsent();
1098
+ const consentRows = Object.entries(consent).map(
1099
+ ([k, v]) => [k, v === true ? "\u2713 granted" : v === false ? "\u2717 denied" : "\u25CB pending"]
1100
+ );
1101
+ if (consentRows.length > 0) {
1102
+ addSection("Consent", consentRows);
1103
+ }
1104
+ if (!event) {
1105
+ tp.appendChild(el("div", { class: "jd-consent-info" }, "Context will populate after the first event fires"));
1106
+ }
1107
+ }
1108
+ const unsubStore = store.onUpdate(() => {
1109
+ render();
1110
+ });
1111
+ document.body.appendChild(host);
1112
+ if (isOpen) {
1113
+ fab.style.display = "none";
1114
+ }
1115
+ render();
1116
+ function destroy() {
1117
+ unsubStore();
1118
+ host.remove();
1119
+ }
1120
+ return { host, open, close, toggle, destroy };
1121
+ }
1122
+
1123
+ // src/index.ts
1124
+ function parseShortcut(shortcut) {
1125
+ const parts = shortcut.toLowerCase().split("+").map((s) => s.trim());
1126
+ const key = parts.pop() ?? "";
1127
+ return {
1128
+ ctrl: parts.includes("ctrl") || parts.includes("control"),
1129
+ meta: parts.includes("meta") || parts.includes("cmd") || parts.includes("command"),
1130
+ shift: parts.includes("shift"),
1131
+ alt: parts.includes("alt") || parts.includes("option"),
1132
+ key
1133
+ };
1134
+ }
1135
+ function matchesShortcut(event, def) {
1136
+ return event.ctrlKey === def.ctrl && event.metaKey === def.meta && event.shiftKey === def.shift && event.altKey === def.alt && event.key.toLowerCase() === def.key;
1137
+ }
1138
+ function createDebugPanel(collector, options) {
1139
+ if (typeof window === "undefined" || typeof document === "undefined") {
1140
+ const noopStore = {
1141
+ getEntries: () => [],
1142
+ getByType: () => [],
1143
+ getCounters: () => ({
1144
+ total: 0,
1145
+ valid: 0,
1146
+ invalid: 0,
1147
+ sent: {},
1148
+ errors: {},
1149
+ consentChanges: 0,
1150
+ queueFlushes: 0
1151
+ }),
1152
+ clear: () => {
1153
+ },
1154
+ onUpdate: () => () => {
1155
+ },
1156
+ destroy: () => {
1157
+ }
1158
+ };
1159
+ return {
1160
+ open: () => {
1161
+ },
1162
+ close: () => {
1163
+ },
1164
+ toggle: () => {
1165
+ },
1166
+ destroy: () => {
1167
+ },
1168
+ store: noopStore
1169
+ };
1170
+ }
1171
+ const maxEvents = options?.maxEvents ?? 500;
1172
+ const position = options?.position ?? "bottom-right";
1173
+ const startOpen = options?.startOpen ?? false;
1174
+ const shortcut = options?.shortcut ?? "ctrl+shift+j";
1175
+ const store = createDebugStore(collector, maxEvents);
1176
+ const panel = createPanel(collector, store, { position, startOpen });
1177
+ const shortcutDef = parseShortcut(shortcut);
1178
+ function onKeyDown(e) {
1179
+ if (matchesShortcut(e, shortcutDef)) {
1180
+ e.preventDefault();
1181
+ panel.toggle();
1182
+ }
1183
+ }
1184
+ document.addEventListener("keydown", onKeyDown);
1185
+ return {
1186
+ open: panel.open,
1187
+ close: panel.close,
1188
+ toggle: panel.toggle,
1189
+ destroy() {
1190
+ document.removeEventListener("keydown", onKeyDown);
1191
+ store.destroy();
1192
+ panel.destroy();
1193
+ },
1194
+ store
1195
+ };
1196
+ }
1197
+ export {
1198
+ createDebugPanel
1199
+ };
1200
+ //# sourceMappingURL=index.js.map