@pie-players/pie-section-player-tools-event-debugger 0.1.1

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.
@@ -0,0 +1,741 @@
1
+ <svelte:options
2
+ customElement={{
3
+ tag: "pie-section-player-tools-event-debugger",
4
+ shadow: "none",
5
+ props: {
6
+ maxEvents: { type: "Number", attribute: "max-events" },
7
+ toolkitCoordinator: { type: "Object", attribute: "toolkit-coordinator" },
8
+ sectionId: { type: "String", attribute: "section-id" },
9
+ attemptId: { type: "String", attribute: "attempt-id" },
10
+ },
11
+ }}
12
+ />
13
+
14
+ <script lang="ts">
15
+ import "@pie-players/pie-theme/components.css";
16
+ import { createEventDispatcher, onDestroy, onMount } from "svelte";
17
+
18
+ type ControllerEvent = {
19
+ type?: string;
20
+ timestamp?: number;
21
+ itemId?: string;
22
+ canonicalItemId?: string;
23
+ intent?: string;
24
+ replayed?: boolean;
25
+ [key: string]: unknown;
26
+ };
27
+
28
+ type EventType =
29
+ | "item-session-data-changed"
30
+ | "item-session-meta-changed"
31
+ | "item-selected"
32
+ | "content-loaded"
33
+ | "item-player-error"
34
+ | "item-complete-changed"
35
+ | "section-loading-complete"
36
+ | "section-items-complete-changed"
37
+ | "section-error";
38
+ type EventLevel = "item" | "section";
39
+
40
+ type EventRecord = {
41
+ id: number;
42
+ type: EventType;
43
+ timestamp: number;
44
+ targetTag: string | null;
45
+ itemId: string | null;
46
+ canonicalItemId: string | null;
47
+ intent: string | null;
48
+ replayed: boolean;
49
+ duplicateCount: number;
50
+ payload: unknown;
51
+ fingerprint: string;
52
+ semanticFingerprint: string;
53
+ };
54
+
55
+ const dispatch = createEventDispatcher<{ close: undefined }>();
56
+
57
+ let {
58
+ maxEvents = 200,
59
+ toolkitCoordinator = null,
60
+ sectionId = "",
61
+ attemptId = undefined,
62
+ }: {
63
+ maxEvents?: number;
64
+ toolkitCoordinator?: any;
65
+ sectionId?: string;
66
+ attemptId?: string;
67
+ } = $props();
68
+ let panelX = $state(380);
69
+ let panelY = $state(100);
70
+ let panelWidth = $state(500);
71
+ let panelHeight = $state(620);
72
+ let isMinimized = $state(false);
73
+ let isPaused = $state(false);
74
+ let selectedLevel = $state<EventLevel>("item");
75
+ let selectedRecordId = $state<number | null>(null);
76
+ let records = $state<EventRecord[]>([]);
77
+ let controllerAvailable = $state(false);
78
+
79
+ let isDragging = false;
80
+ let isResizing = false;
81
+ let dragStartX = 0;
82
+ let dragStartY = 0;
83
+ let dragStartPanelX = 0;
84
+ let dragStartPanelY = 0;
85
+ let resizeStartX = 0;
86
+ let resizeStartY = 0;
87
+ let resizeStartWidth = 0;
88
+ let resizeStartHeight = 0;
89
+ let nextRecordId = 1;
90
+ let activeController: {
91
+ subscribe?: (listener: (event: ControllerEvent) => void) => () => void;
92
+ } | null = null;
93
+ let unsubscribeController: (() => void) | null = null;
94
+ let unsubscribeLifecycle: (() => void) | null = null;
95
+
96
+ function safeClone<T>(value: T): T {
97
+ try {
98
+ return structuredClone(value);
99
+ } catch {
100
+ try {
101
+ return JSON.parse(JSON.stringify(value)) as T;
102
+ } catch {
103
+ return value;
104
+ }
105
+ }
106
+ }
107
+
108
+ function createFingerprint(type: EventType, payload: unknown): string {
109
+ let payloadString = "";
110
+ try {
111
+ payloadString = JSON.stringify(payload);
112
+ } catch {
113
+ payloadString = String(payload);
114
+ }
115
+ return `${type}:${payloadString}`;
116
+ }
117
+
118
+ function createSemanticFingerprint(type: EventType, payload: unknown): string {
119
+ const semantic =
120
+ payload && typeof payload === "object"
121
+ ? { ...(payload as Record<string, unknown>) }
122
+ : payload;
123
+ if (semantic && typeof semantic === "object") {
124
+ delete (semantic as Record<string, unknown>).timestamp;
125
+ delete (semantic as Record<string, unknown>).sourceRuntimeId;
126
+ }
127
+ let payloadString = "";
128
+ try {
129
+ payloadString = JSON.stringify(semantic);
130
+ } catch {
131
+ payloadString = String(semantic);
132
+ }
133
+ return `${type}:${payloadString}`;
134
+ }
135
+
136
+ function normalizeEventType(input: unknown): EventType | null {
137
+ const value = String(input || "");
138
+ if (
139
+ value === "item-session-data-changed" ||
140
+ value === "item-session-meta-changed" ||
141
+ value === "item-selected" ||
142
+ value === "content-loaded" ||
143
+ value === "item-player-error" ||
144
+ value === "item-complete-changed" ||
145
+ value === "section-loading-complete" ||
146
+ value === "section-items-complete-changed" ||
147
+ value === "section-error"
148
+ ) {
149
+ return value;
150
+ }
151
+ return null;
152
+ }
153
+
154
+ function getEventLevel(type: EventType): EventLevel {
155
+ if (
156
+ type === "section-loading-complete" ||
157
+ type === "section-items-complete-changed" ||
158
+ type === "section-error"
159
+ ) {
160
+ return "section";
161
+ }
162
+ return "item";
163
+ }
164
+
165
+ function getValueAsString(value: unknown): string | null {
166
+ return typeof value === "string" && value.trim() ? value : null;
167
+ }
168
+
169
+ function normalizeRecord(detail: ControllerEvent, type: EventType): EventRecord {
170
+ const payload = safeClone((detail || {}) as unknown);
171
+ const fingerprint = createFingerprint(type, payload);
172
+ const semanticFingerprint = createSemanticFingerprint(type, payload);
173
+ return {
174
+ id: nextRecordId++,
175
+ type,
176
+ timestamp: typeof detail.timestamp === "number" ? detail.timestamp : Date.now(),
177
+ targetTag: "section-controller",
178
+ itemId: getValueAsString(detail?.itemId),
179
+ canonicalItemId: getValueAsString(detail?.canonicalItemId),
180
+ intent: getValueAsString(detail?.intent),
181
+ replayed: detail?.replayed === true,
182
+ duplicateCount: 1,
183
+ payload,
184
+ fingerprint,
185
+ semanticFingerprint,
186
+ };
187
+ }
188
+
189
+ function pushRecord(detail: ControllerEvent) {
190
+ if (isPaused) return;
191
+ const type = normalizeEventType(detail?.type);
192
+ if (!type) return;
193
+ const next = normalizeRecord(detail, type);
194
+ const latest = records[0];
195
+ if (latest && latest.fingerprint === next.fingerprint) {
196
+ records = [
197
+ {
198
+ ...latest,
199
+ timestamp: next.timestamp,
200
+ duplicateCount: latest.duplicateCount + 1,
201
+ },
202
+ ...records.slice(1),
203
+ ];
204
+ return;
205
+ }
206
+ const cappedMaxEvents = Math.max(10, Math.min(2000, maxEvents || 200));
207
+ records = [next, ...records].slice(0, cappedMaxEvents);
208
+ if (selectedRecordId == null) {
209
+ selectedRecordId = next.id;
210
+ }
211
+ }
212
+
213
+ function getController(): any | null {
214
+ if (!toolkitCoordinator || !sectionId) return null;
215
+ return (
216
+ toolkitCoordinator.getSectionController?.({
217
+ sectionId,
218
+ attemptId,
219
+ }) || null
220
+ );
221
+ }
222
+
223
+ function detachControllerSubscription() {
224
+ unsubscribeController?.();
225
+ unsubscribeController = null;
226
+ activeController = null;
227
+ }
228
+
229
+ function detachLifecycleSubscription() {
230
+ unsubscribeLifecycle?.();
231
+ unsubscribeLifecycle = null;
232
+ }
233
+
234
+ function ensureControllerSubscription() {
235
+ const controller = getController();
236
+ controllerAvailable = Boolean(controller);
237
+ if (!controller) {
238
+ detachControllerSubscription();
239
+ return;
240
+ }
241
+ if (controller === activeController) return;
242
+ detachControllerSubscription();
243
+ activeController = controller;
244
+ unsubscribeController =
245
+ controller.subscribe?.((event: ControllerEvent) => {
246
+ pushRecord(event || {});
247
+ }) || null;
248
+ }
249
+
250
+ function startDrag(event: MouseEvent) {
251
+ isDragging = true;
252
+ dragStartX = event.clientX;
253
+ dragStartY = event.clientY;
254
+ dragStartPanelX = panelX;
255
+ dragStartPanelY = panelY;
256
+ document.addEventListener("mousemove", onDrag);
257
+ document.addEventListener("mouseup", stopDrag);
258
+ }
259
+
260
+ function onDrag(event: MouseEvent) {
261
+ if (!isDragging) return;
262
+ const deltaX = event.clientX - dragStartX;
263
+ const deltaY = event.clientY - dragStartY;
264
+ panelX = Math.max(0, Math.min(dragStartPanelX + deltaX, window.innerWidth - panelWidth));
265
+ panelY = Math.max(0, Math.min(dragStartPanelY + deltaY, window.innerHeight - 100));
266
+ }
267
+
268
+ function stopDrag() {
269
+ isDragging = false;
270
+ document.removeEventListener("mousemove", onDrag);
271
+ document.removeEventListener("mouseup", stopDrag);
272
+ }
273
+
274
+ function startResize(event: MouseEvent) {
275
+ isResizing = true;
276
+ resizeStartX = event.clientX;
277
+ resizeStartY = event.clientY;
278
+ resizeStartWidth = panelWidth;
279
+ resizeStartHeight = panelHeight;
280
+ document.addEventListener("mousemove", onResize);
281
+ document.addEventListener("mouseup", stopResize);
282
+ event.preventDefault();
283
+ event.stopPropagation();
284
+ }
285
+
286
+ function onResize(event: MouseEvent) {
287
+ if (!isResizing) return;
288
+ const deltaX = event.clientX - resizeStartX;
289
+ const deltaY = event.clientY - resizeStartY;
290
+ panelWidth = Math.max(340, Math.min(resizeStartWidth + deltaX, window.innerWidth - panelX));
291
+ panelHeight = Math.max(260, Math.min(resizeStartHeight + deltaY, window.innerHeight - panelY));
292
+ }
293
+
294
+ function stopResize() {
295
+ isResizing = false;
296
+ document.removeEventListener("mousemove", onResize);
297
+ document.removeEventListener("mouseup", stopResize);
298
+ }
299
+
300
+ function clearRecords() {
301
+ records = [];
302
+ selectedRecordId = null;
303
+ }
304
+
305
+ function formatTimestamp(timestamp: number): string {
306
+ return new Date(timestamp).toLocaleTimeString();
307
+ }
308
+
309
+ const visibleRecords = $derived.by(() =>
310
+ records.filter(
311
+ (record) => getEventLevel(record.type) === selectedLevel,
312
+ ),
313
+ );
314
+ const semanticCounts = $derived.by(() => {
315
+ const counts = new Map<string, number>();
316
+ for (const record of visibleRecords) {
317
+ counts.set(
318
+ record.semanticFingerprint,
319
+ (counts.get(record.semanticFingerprint) || 0) + record.duplicateCount,
320
+ );
321
+ }
322
+ return counts;
323
+ });
324
+ const selectedRecord = $derived.by(
325
+ () => visibleRecords.find((record) => record.id === selectedRecordId) || visibleRecords[0] || null,
326
+ );
327
+
328
+ onMount(() => {
329
+ const clamp = (value: number, min: number, max: number) => Math.max(min, Math.min(value, max));
330
+ const viewportWidth = window.innerWidth;
331
+ const viewportHeight = window.innerHeight;
332
+ panelWidth = clamp(Math.round(viewportWidth * 0.34), 380, 720);
333
+ panelHeight = clamp(Math.round(viewportHeight * 0.74), 360, 860);
334
+ panelX = Math.max(16, Math.round(viewportWidth * 0.58));
335
+ panelY = Math.max(16, Math.round((viewportHeight - panelHeight) / 2));
336
+ ensureControllerSubscription();
337
+ unsubscribeLifecycle = toolkitCoordinator?.onSectionControllerLifecycle?.(
338
+ (event: { key?: { sectionId?: string; attemptId?: string } }) => {
339
+ const eventSectionId = event?.key?.sectionId || "";
340
+ const eventAttemptId = event?.key?.attemptId || undefined;
341
+ if (eventSectionId !== sectionId) return;
342
+ if ((eventAttemptId || undefined) !== (attemptId || undefined)) return;
343
+ ensureControllerSubscription();
344
+ },
345
+ );
346
+
347
+ return () => {
348
+ detachControllerSubscription();
349
+ detachLifecycleSubscription();
350
+ };
351
+ });
352
+
353
+ $effect(() => {
354
+ void toolkitCoordinator;
355
+ void sectionId;
356
+ void attemptId;
357
+ ensureControllerSubscription();
358
+ detachLifecycleSubscription();
359
+ unsubscribeLifecycle = toolkitCoordinator?.onSectionControllerLifecycle?.(
360
+ (event: { key?: { sectionId?: string; attemptId?: string } }) => {
361
+ const eventSectionId = event?.key?.sectionId || "";
362
+ const eventAttemptId = event?.key?.attemptId || undefined;
363
+ if (eventSectionId !== sectionId) return;
364
+ if ((eventAttemptId || undefined) !== (attemptId || undefined)) return;
365
+ ensureControllerSubscription();
366
+ },
367
+ );
368
+ return () => {
369
+ detachLifecycleSubscription();
370
+ };
371
+ });
372
+
373
+ onDestroy(() => {
374
+ stopDrag();
375
+ stopResize();
376
+ detachControllerSubscription();
377
+ detachLifecycleSubscription();
378
+ });
379
+ </script>
380
+
381
+ <div
382
+ class="pie-section-player-tools-event-debugger"
383
+ style="left: {panelX}px; top: {panelY}px; width: {panelWidth}px; {isMinimized ? 'height: auto;' : `height: ${panelHeight}px;`}"
384
+ >
385
+ <div
386
+ class="pie-section-player-tools-event-debugger__header"
387
+ onmousedown={startDrag}
388
+ role="button"
389
+ tabindex="0"
390
+ aria-label="Drag event debugger panel"
391
+ >
392
+ <div class="pie-section-player-tools-event-debugger__header-title">
393
+ <h3 class="pie-section-player-tools-event-debugger__title">Session Broadcasts</h3>
394
+ <span class="pie-section-player-tools-event-debugger__counter">{records.length}</span>
395
+ </div>
396
+ <div class="pie-section-player-tools-event-debugger__header-actions">
397
+ <button
398
+ class="pie-section-player-tools-event-debugger__icon-button"
399
+ onclick={() => (isMinimized = !isMinimized)}
400
+ title={isMinimized ? "Maximize" : "Minimize"}
401
+ >
402
+ {isMinimized ? "▴" : "▾"}
403
+ </button>
404
+ <button
405
+ class="pie-section-player-tools-event-debugger__icon-button"
406
+ onclick={() => dispatch("close")}
407
+ title="Close"
408
+ >
409
+
410
+ </button>
411
+ </div>
412
+ </div>
413
+
414
+ {#if !isMinimized}
415
+ <div class="pie-section-player-tools-event-debugger__content-shell" style="height: {panelHeight - 56}px;">
416
+ <div class="pie-section-player-tools-event-debugger__toolbar">
417
+ <div
418
+ class="pie-section-player-tools-event-debugger__toggle-group"
419
+ role="group"
420
+ aria-label="Event level filter"
421
+ >
422
+ <button
423
+ class="pie-section-player-tools-event-debugger__toggle-button"
424
+ class:pie-section-player-tools-event-debugger__toggle-button--active={selectedLevel ===
425
+ "item"}
426
+ onclick={() => (selectedLevel = "item")}
427
+ aria-pressed={selectedLevel === "item"}
428
+ >
429
+ item
430
+ </button>
431
+ <button
432
+ class="pie-section-player-tools-event-debugger__toggle-button"
433
+ class:pie-section-player-tools-event-debugger__toggle-button--active={selectedLevel ===
434
+ "section"}
435
+ onclick={() => (selectedLevel = "section")}
436
+ aria-pressed={selectedLevel === "section"}
437
+ >
438
+ section
439
+ </button>
440
+ </div>
441
+ <button class="pie-section-player-tools-event-debugger__button" onclick={() => (isPaused = !isPaused)}>
442
+ {isPaused ? "resume" : "pause"}
443
+ </button>
444
+ <button class="pie-section-player-tools-event-debugger__button" onclick={clearRecords}>
445
+ clear
446
+ </button>
447
+ {#if !controllerAvailable}
448
+ <span class="pie-section-player-tools-event-debugger__status">
449
+ controller unavailable
450
+ </span>
451
+ {/if}
452
+ </div>
453
+
454
+ <div class="pie-section-player-tools-event-debugger__grid">
455
+ <div class="pie-section-player-tools-event-debugger__list">
456
+ {#if visibleRecords.length === 0}
457
+ <div class="pie-section-player-tools-event-debugger__empty">
458
+ No matching events yet. Interact with an item to capture broadcasts.
459
+ </div>
460
+ {:else}
461
+ {#each visibleRecords as record (record.id)}
462
+ <button
463
+ class="pie-section-player-tools-event-debugger__row"
464
+ class:pie-section-player-tools-event-debugger__row--active={selectedRecord?.id ===
465
+ record.id}
466
+ onclick={() => (selectedRecordId = record.id)}
467
+ >
468
+ <div class="pie-section-player-tools-event-debugger__row-top">
469
+ <span class="pie-section-player-tools-event-debugger__event-type">{record.type}</span>
470
+ <span class="pie-section-player-tools-event-debugger__event-time">
471
+ {formatTimestamp(record.timestamp)}
472
+ </span>
473
+ </div>
474
+ <div class="pie-section-player-tools-event-debugger__row-meta">
475
+ {#if record.itemId}
476
+ <span>item: {record.itemId}</span>
477
+ {/if}
478
+ {#if record.replayed}
479
+ <span>replayed</span>
480
+ {/if}
481
+ {#if record.intent}
482
+ <span>intent: {record.intent}</span>
483
+ {/if}
484
+ {#if (semanticCounts.get(record.semanticFingerprint) || 0) > record.duplicateCount}
485
+ <span>
486
+ semantic repeats: {semanticCounts.get(record.semanticFingerprint)}
487
+ </span>
488
+ {/if}
489
+ {#if record.duplicateCount > 1}
490
+ <span>dupes: {record.duplicateCount}</span>
491
+ {/if}
492
+ </div>
493
+ </button>
494
+ {/each}
495
+ {/if}
496
+ </div>
497
+ <div class="pie-section-player-tools-event-debugger__detail">
498
+ {#if selectedRecord}
499
+ <div class="pie-section-player-tools-event-debugger__detail-meta">
500
+ <div><strong>Type:</strong> {selectedRecord.type}</div>
501
+ <div><strong>Target:</strong> {selectedRecord.targetTag || "unknown"}</div>
502
+ <div><strong>Item:</strong> {selectedRecord.itemId || "n/a"}</div>
503
+ <div><strong>Canonical:</strong> {selectedRecord.canonicalItemId || "n/a"}</div>
504
+ <div><strong>Replayed:</strong> {selectedRecord.replayed ? "yes" : "no"}</div>
505
+ <div><strong>Intent:</strong> {selectedRecord.intent || "n/a"}</div>
506
+ <div><strong>Duplicates:</strong> {selectedRecord.duplicateCount}</div>
507
+ <div>
508
+ <strong>Semantic Repeats:</strong>
509
+ {semanticCounts.get(selectedRecord.semanticFingerprint) || selectedRecord.duplicateCount}
510
+ </div>
511
+ </div>
512
+ <pre class="pie-section-player-tools-event-debugger__pre">{JSON.stringify(
513
+ selectedRecord.payload,
514
+ null,
515
+ 2,
516
+ )}</pre>
517
+ {:else}
518
+ <div class="pie-section-player-tools-event-debugger__empty">
519
+ Select an event to inspect payload details.
520
+ </div>
521
+ {/if}
522
+ </div>
523
+ </div>
524
+ </div>
525
+ {/if}
526
+
527
+ {#if !isMinimized}
528
+ <div
529
+ class="pie-section-player-tools-event-debugger__resize-handle"
530
+ onmousedown={startResize}
531
+ role="button"
532
+ tabindex="0"
533
+ title="Resize window"
534
+ >
535
+
536
+ </div>
537
+ {/if}
538
+ </div>
539
+
540
+ <style>
541
+ .pie-section-player-tools-event-debugger {
542
+ position: fixed;
543
+ z-index: 9999;
544
+ background: var(--color-base-100, #fff);
545
+ color: var(--color-base-content, #1f2937);
546
+ border: 1px solid var(--color-base-300, #d1d5db);
547
+ border-radius: 8px;
548
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
549
+ overflow: hidden;
550
+ font-family: var(--pie-font-family, Inter, system-ui, sans-serif);
551
+ }
552
+
553
+ .pie-section-player-tools-event-debugger__header {
554
+ height: 56px;
555
+ padding: 0 12px;
556
+ display: flex;
557
+ align-items: center;
558
+ justify-content: space-between;
559
+ background: var(--color-base-200, #f3f4f6);
560
+ cursor: move;
561
+ user-select: none;
562
+ }
563
+
564
+ .pie-section-player-tools-event-debugger__header-title {
565
+ display: flex;
566
+ align-items: center;
567
+ gap: 8px;
568
+ }
569
+
570
+ .pie-section-player-tools-event-debugger__title {
571
+ margin: 0;
572
+ font-size: 0.95rem;
573
+ font-weight: 700;
574
+ }
575
+
576
+ .pie-section-player-tools-event-debugger__counter {
577
+ font-size: 0.75rem;
578
+ padding: 2px 7px;
579
+ border-radius: 999px;
580
+ background: var(--color-primary, #2563eb);
581
+ color: white;
582
+ }
583
+
584
+ .pie-section-player-tools-event-debugger__header-actions {
585
+ display: flex;
586
+ gap: 6px;
587
+ }
588
+
589
+ .pie-section-player-tools-event-debugger__icon-button {
590
+ border: 1px solid var(--color-base-300, #d1d5db);
591
+ background: var(--color-base-100, #fff);
592
+ border-radius: 6px;
593
+ font-size: 0.8rem;
594
+ padding: 4px 7px;
595
+ cursor: pointer;
596
+ }
597
+
598
+ .pie-section-player-tools-event-debugger__content-shell {
599
+ display: flex;
600
+ flex-direction: column;
601
+ min-height: 0;
602
+ }
603
+
604
+ .pie-section-player-tools-event-debugger__toolbar {
605
+ display: flex;
606
+ align-items: center;
607
+ gap: 8px;
608
+ padding: 10px 12px;
609
+ border-bottom: 1px solid var(--color-base-300, #d1d5db);
610
+ flex-wrap: wrap;
611
+ }
612
+
613
+ .pie-section-player-tools-event-debugger__button {
614
+ border: 1px solid var(--color-base-300, #d1d5db);
615
+ background: var(--color-base-100, #fff);
616
+ color: inherit;
617
+ border-radius: 6px;
618
+ font-size: 0.78rem;
619
+ padding: 6px 8px;
620
+ }
621
+
622
+ .pie-section-player-tools-event-debugger__toggle-group {
623
+ display: inline-flex;
624
+ border: 1px solid var(--color-base-300, #d1d5db);
625
+ border-radius: 6px;
626
+ overflow: hidden;
627
+ }
628
+
629
+ .pie-section-player-tools-event-debugger__toggle-button {
630
+ border: none;
631
+ background: var(--color-base-100, #fff);
632
+ color: inherit;
633
+ font-size: 0.78rem;
634
+ padding: 6px 10px;
635
+ cursor: pointer;
636
+ }
637
+
638
+ .pie-section-player-tools-event-debugger__toggle-button + .pie-section-player-tools-event-debugger__toggle-button {
639
+ border-left: 1px solid var(--color-base-300, #d1d5db);
640
+ }
641
+
642
+ .pie-section-player-tools-event-debugger__toggle-button--active {
643
+ background: color-mix(in srgb, var(--color-primary, #2563eb) 18%, transparent);
644
+ font-weight: 600;
645
+ }
646
+
647
+ .pie-section-player-tools-event-debugger__status {
648
+ font-size: 0.72rem;
649
+ opacity: 0.75;
650
+ }
651
+
652
+ .pie-section-player-tools-event-debugger__grid {
653
+ display: grid;
654
+ grid-template-columns: minmax(180px, 1fr) minmax(260px, 1.3fr);
655
+ flex: 1;
656
+ min-height: 0;
657
+ }
658
+
659
+ .pie-section-player-tools-event-debugger__list {
660
+ border-right: 1px solid var(--color-base-300, #d1d5db);
661
+ overflow: auto;
662
+ }
663
+
664
+ .pie-section-player-tools-event-debugger__detail {
665
+ overflow: auto;
666
+ }
667
+
668
+ .pie-section-player-tools-event-debugger__row {
669
+ display: block;
670
+ width: 100%;
671
+ border: 0;
672
+ text-align: left;
673
+ background: transparent;
674
+ padding: 8px 10px;
675
+ border-bottom: 1px solid var(--color-base-300, #e5e7eb);
676
+ cursor: pointer;
677
+ }
678
+
679
+ .pie-section-player-tools-event-debugger__row--active {
680
+ background: color-mix(in srgb, var(--color-primary, #2563eb) 14%, transparent);
681
+ }
682
+
683
+ .pie-section-player-tools-event-debugger__row-top {
684
+ display: flex;
685
+ justify-content: space-between;
686
+ gap: 8px;
687
+ font-size: 0.74rem;
688
+ }
689
+
690
+ .pie-section-player-tools-event-debugger__event-type {
691
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
692
+ font-weight: 600;
693
+ }
694
+
695
+ .pie-section-player-tools-event-debugger__event-time {
696
+ opacity: 0.75;
697
+ }
698
+
699
+ .pie-section-player-tools-event-debugger__row-meta {
700
+ margin-top: 4px;
701
+ display: flex;
702
+ flex-wrap: wrap;
703
+ gap: 8px;
704
+ font-size: 0.7rem;
705
+ opacity: 0.88;
706
+ }
707
+
708
+ .pie-section-player-tools-event-debugger__detail-meta {
709
+ display: grid;
710
+ gap: 3px;
711
+ padding: 10px 12px;
712
+ font-size: 0.78rem;
713
+ border-bottom: 1px solid var(--color-base-300, #d1d5db);
714
+ }
715
+
716
+ .pie-section-player-tools-event-debugger__pre {
717
+ margin: 0;
718
+ padding: 12px;
719
+ font-size: 0.74rem;
720
+ line-height: 1.35;
721
+ white-space: pre-wrap;
722
+ word-break: break-word;
723
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
724
+ }
725
+
726
+ .pie-section-player-tools-event-debugger__empty {
727
+ padding: 12px;
728
+ font-size: 0.8rem;
729
+ opacity: 0.8;
730
+ }
731
+
732
+ .pie-section-player-tools-event-debugger__resize-handle {
733
+ position: absolute;
734
+ right: 6px;
735
+ bottom: 4px;
736
+ cursor: nwse-resize;
737
+ user-select: none;
738
+ font-size: 0.9rem;
739
+ opacity: 0.7;
740
+ }
741
+ </style>