state-surgeon 1.0.0 → 2.0.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.
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
- var React2 = require('react');
4
+ var React4 = require('react');
5
5
 
6
6
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
7
 
8
- var React2__default = /*#__PURE__*/_interopDefault(React2);
8
+ var React4__default = /*#__PURE__*/_interopDefault(React4);
9
9
 
10
10
  // src/dashboard/components/MutationInspector.tsx
11
11
  function MutationInspector({
@@ -323,26 +323,208 @@ function calculateDiff(before, after, path = "") {
323
323
  }
324
324
  return diffs;
325
325
  }
326
+ function detectAnomaly(value) {
327
+ if (typeof value === "number" && isNaN(value)) return "nan";
328
+ if (value === void 0) return "undefined";
329
+ if (value === null) return "null";
330
+ if (value === "") return "empty-string";
331
+ if (typeof value === "number" && value < 0) return "negative";
332
+ return null;
333
+ }
334
+ function getAnomalyBadge(anomaly) {
335
+ switch (anomaly) {
336
+ case "nan":
337
+ return "\u26A0\uFE0F NaN";
338
+ case "undefined":
339
+ return "\u26A0\uFE0F undefined";
340
+ case "null":
341
+ return "\u26A1 null";
342
+ case "empty-string":
343
+ return "\u{1F4ED} empty";
344
+ case "negative":
345
+ return "\u2796 negative";
346
+ default:
347
+ return "";
348
+ }
349
+ }
326
350
  function StateDiffViewer({
327
351
  previousState,
328
352
  nextState,
329
353
  diff: preDiff,
330
354
  defaultExpandDepth = 3,
355
+ viewMode = "split",
331
356
  className = ""
332
357
  }) {
333
- const diff = React2.useMemo(
358
+ const [activeTab, setActiveTab] = React4.useState("diff");
359
+ const [searchQuery, setSearchQuery] = React4.useState("");
360
+ const [copiedPath, setCopiedPath] = React4.useState(null);
361
+ const diff = React4.useMemo(
334
362
  () => preDiff || calculateDiff(previousState, nextState),
335
363
  [previousState, nextState, preDiff]
336
364
  );
337
- const changedPaths = React2.useMemo(
365
+ const changedPaths = React4.useMemo(
338
366
  () => new Set(diff.map((d) => d.path)),
339
367
  [diff]
340
368
  );
341
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `state-diff-viewer ${className}`, children: [
342
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "state-diff-viewer__panels", children: [
343
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "state-diff-viewer__panel", children: [
344
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "state-diff-viewer__title", children: "Before" }),
345
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "state-diff-viewer__content", children: /* @__PURE__ */ jsxRuntime.jsx(
369
+ const groupedDiffs = React4.useMemo(() => {
370
+ const adds = diff.filter((d) => d.operation === "ADD");
371
+ const updates = diff.filter((d) => d.operation === "UPDATE");
372
+ const removes = diff.filter((d) => d.operation === "REMOVE");
373
+ return { adds, updates, removes };
374
+ }, [diff]);
375
+ const anomalies = React4.useMemo(() => {
376
+ const result = [];
377
+ for (const d of diff) {
378
+ if (d.operation !== "REMOVE") {
379
+ const anomaly = detectAnomaly(d.newValue);
380
+ if (anomaly) {
381
+ result.push({ path: d.path, type: anomaly, value: d.newValue });
382
+ }
383
+ }
384
+ }
385
+ return result;
386
+ }, [diff]);
387
+ const copyToClipboard = React4.useCallback((value, path) => {
388
+ const text = typeof value === "object" ? JSON.stringify(value, null, 2) : String(value);
389
+ navigator.clipboard.writeText(text);
390
+ setCopiedPath(path);
391
+ setTimeout(() => setCopiedPath(null), 2e3);
392
+ }, []);
393
+ const copyFullState = React4.useCallback((state, label) => {
394
+ navigator.clipboard.writeText(JSON.stringify(state, null, 2));
395
+ setCopiedPath(label);
396
+ setTimeout(() => setCopiedPath(null), 2e3);
397
+ }, []);
398
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `state-diff-viewer-enhanced ${className}`, children: [
399
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-header", children: [
400
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-tabs", children: [
401
+ /* @__PURE__ */ jsxRuntime.jsxs(
402
+ "button",
403
+ {
404
+ className: `sdv-tab ${activeTab === "diff" ? "active" : ""}`,
405
+ onClick: () => setActiveTab("diff"),
406
+ children: [
407
+ "\u{1F4CA} Changes (",
408
+ diff.length,
409
+ ")"
410
+ ]
411
+ }
412
+ ),
413
+ /* @__PURE__ */ jsxRuntime.jsx(
414
+ "button",
415
+ {
416
+ className: `sdv-tab ${activeTab === "before" ? "active" : ""}`,
417
+ onClick: () => setActiveTab("before"),
418
+ children: "\u23EA Before"
419
+ }
420
+ ),
421
+ /* @__PURE__ */ jsxRuntime.jsx(
422
+ "button",
423
+ {
424
+ className: `sdv-tab ${activeTab === "after" ? "active" : ""}`,
425
+ onClick: () => setActiveTab("after"),
426
+ children: "\u23E9 After"
427
+ }
428
+ ),
429
+ /* @__PURE__ */ jsxRuntime.jsx(
430
+ "button",
431
+ {
432
+ className: `sdv-tab ${activeTab === "raw" ? "active" : ""}`,
433
+ onClick: () => setActiveTab("raw"),
434
+ children: "\u{1F4DD} Raw JSON"
435
+ }
436
+ )
437
+ ] }),
438
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sdv-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
439
+ "input",
440
+ {
441
+ type: "text",
442
+ placeholder: "\u{1F50D} Search paths...",
443
+ value: searchQuery,
444
+ onChange: (e) => setSearchQuery(e.target.value),
445
+ className: "sdv-search"
446
+ }
447
+ ) })
448
+ ] }),
449
+ anomalies.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-anomalies", children: [
450
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-anomaly-header", children: [
451
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sdv-anomaly-icon", children: "\u{1F6A8}" }),
452
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
453
+ "Potential Issues Detected (",
454
+ anomalies.length,
455
+ ")"
456
+ ] })
457
+ ] }),
458
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sdv-anomaly-list", children: anomalies.map((a, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `sdv-anomaly-item sdv-anomaly-${a.type}`, children: [
459
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: a.path }),
460
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sdv-anomaly-badge", children: getAnomalyBadge(a.type) })
461
+ ] }, i)) })
462
+ ] }),
463
+ activeTab === "diff" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-summary", children: [
464
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sdv-summary-item sdv-add", children: [
465
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sdv-dot" }),
466
+ groupedDiffs.adds.length,
467
+ " added"
468
+ ] }),
469
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sdv-summary-item sdv-update", children: [
470
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sdv-dot" }),
471
+ groupedDiffs.updates.length,
472
+ " changed"
473
+ ] }),
474
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sdv-summary-item sdv-remove", children: [
475
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sdv-dot" }),
476
+ groupedDiffs.removes.length,
477
+ " removed"
478
+ ] })
479
+ ] }),
480
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-content", children: [
481
+ activeTab === "diff" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sdv-diff-list", children: diff.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sdv-empty", children: "No changes detected" }) : diff.filter((d) => !searchQuery || d.path.toLowerCase().includes(searchQuery.toLowerCase())).map((change, index) => /* @__PURE__ */ jsxRuntime.jsxs(
482
+ "div",
483
+ {
484
+ className: `sdv-diff-item sdv-diff-${change.operation.toLowerCase()}`,
485
+ children: [
486
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-diff-header", children: [
487
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sdv-diff-op", children: change.operation }),
488
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: "sdv-diff-path", children: change.path }),
489
+ /* @__PURE__ */ jsxRuntime.jsx(
490
+ "button",
491
+ {
492
+ className: "sdv-copy-btn",
493
+ onClick: () => copyToClipboard(change.newValue ?? change.oldValue, change.path),
494
+ title: "Copy value",
495
+ children: copiedPath === change.path ? "\u2713" : "\u{1F4CB}"
496
+ }
497
+ )
498
+ ] }),
499
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-diff-values", children: [
500
+ change.operation === "UPDATE" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
501
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-value sdv-value-old", children: [
502
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sdv-value-label", children: "Before:" }),
503
+ /* @__PURE__ */ jsxRuntime.jsx(ValueDisplay, { value: change.oldValue })
504
+ ] }),
505
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sdv-arrow", children: "\u2192" }),
506
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-value sdv-value-new", children: [
507
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sdv-value-label", children: "After:" }),
508
+ /* @__PURE__ */ jsxRuntime.jsx(ValueDisplay, { value: change.newValue })
509
+ ] })
510
+ ] }),
511
+ change.operation === "ADD" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sdv-value sdv-value-new", children: /* @__PURE__ */ jsxRuntime.jsx(ValueDisplay, { value: change.newValue }) }),
512
+ change.operation === "REMOVE" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sdv-value sdv-value-old", children: /* @__PURE__ */ jsxRuntime.jsx(ValueDisplay, { value: change.oldValue }) })
513
+ ] })
514
+ ]
515
+ },
516
+ index
517
+ )) }),
518
+ activeTab === "before" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-state-view", children: [
519
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sdv-state-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
520
+ "button",
521
+ {
522
+ onClick: () => copyFullState(previousState, "before"),
523
+ className: "sdv-copy-full",
524
+ children: copiedPath === "before" ? "\u2713 Copied!" : "\u{1F4CB} Copy State"
525
+ }
526
+ ) }),
527
+ /* @__PURE__ */ jsxRuntime.jsx(
346
528
  StateTree,
347
529
  {
348
530
  value: previousState,
@@ -351,11 +533,18 @@ function StateDiffViewer({
351
533
  defaultExpandDepth,
352
534
  changeType: "before"
353
535
  }
354
- ) })
536
+ )
355
537
  ] }),
356
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "state-diff-viewer__panel", children: [
357
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "state-diff-viewer__title", children: "After" }),
358
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "state-diff-viewer__content", children: /* @__PURE__ */ jsxRuntime.jsx(
538
+ activeTab === "after" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-state-view", children: [
539
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sdv-state-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
540
+ "button",
541
+ {
542
+ onClick: () => copyFullState(nextState, "after"),
543
+ className: "sdv-copy-full",
544
+ children: copiedPath === "after" ? "\u2713 Copied!" : "\u{1F4CB} Copy State"
545
+ }
546
+ ) }),
547
+ /* @__PURE__ */ jsxRuntime.jsx(
359
548
  StateTree,
360
549
  {
361
550
  value: nextState,
@@ -364,246 +553,510 @@ function StateDiffViewer({
364
553
  defaultExpandDepth,
365
554
  changeType: "after"
366
555
  }
367
- ) })
368
- ] })
369
- ] }),
370
- diff.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "state-diff-viewer__summary", children: [
371
- /* @__PURE__ */ jsxRuntime.jsxs("h4", { children: [
372
- "Changes (",
373
- diff.length,
374
- ")"
556
+ )
375
557
  ] }),
376
- /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "state-diff-viewer__changes", children: diff.map((change, index) => /* @__PURE__ */ jsxRuntime.jsxs(
377
- "li",
378
- {
379
- className: `state-diff-viewer__change state-diff-viewer__change--${change.operation.toLowerCase()}`,
380
- children: [
381
- /* @__PURE__ */ jsxRuntime.jsx("code", { children: change.path }),
382
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-diff-viewer__change-type", children: change.operation }),
383
- change.operation === "UPDATE" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
384
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-diff-viewer__value state-diff-viewer__value--old", children: formatValue(change.oldValue) }),
385
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-diff-viewer__arrow", children: "\u2192" }),
386
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-diff-viewer__value state-diff-viewer__value--new", children: formatValue(change.newValue) })
387
- ] }),
388
- change.operation === "ADD" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-diff-viewer__value state-diff-viewer__value--new", children: formatValue(change.newValue) }),
389
- change.operation === "REMOVE" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-diff-viewer__value state-diff-viewer__value--old", children: formatValue(change.oldValue) })
390
- ]
391
- },
392
- index
393
- )) })
558
+ activeTab === "raw" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sdv-raw-view", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-raw-panels", children: [
559
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-raw-panel", children: [
560
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { children: "Before" }),
561
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { children: JSON.stringify(previousState, null, 2) })
562
+ ] }),
563
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-raw-panel", children: [
564
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { children: "After" }),
565
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { children: JSON.stringify(nextState, null, 2) })
566
+ ] })
567
+ ] }) })
394
568
  ] }),
395
569
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
396
- .state-diff-viewer {
397
- font-family: monospace;
398
- font-size: 0.875rem;
570
+ .state-diff-viewer-enhanced {
571
+ background: #1a1a2e;
572
+ border-radius: 12px;
573
+ overflow: hidden;
574
+ font-family: system-ui, sans-serif;
399
575
  color: #e0e0e0;
400
576
  }
401
577
 
402
- .state-diff-viewer__panels {
403
- display: grid;
404
- grid-template-columns: 1fr 1fr;
405
- gap: 1rem;
578
+ .sdv-header {
579
+ display: flex;
580
+ justify-content: space-between;
581
+ align-items: center;
582
+ padding: 0.75rem 1rem;
583
+ background: #16213e;
584
+ border-bottom: 1px solid #333;
585
+ flex-wrap: wrap;
586
+ gap: 0.5rem;
406
587
  }
407
588
 
408
- .state-diff-viewer__panel {
409
- background: #16213e;
410
- border-radius: 8px;
411
- overflow: hidden;
589
+ .sdv-tabs {
590
+ display: flex;
591
+ gap: 0.25rem;
412
592
  }
413
593
 
414
- .state-diff-viewer__title {
415
- margin: 0;
416
- padding: 0.75rem 1rem;
417
- background: #1a1a2e;
594
+ .sdv-tab {
595
+ padding: 0.5rem 1rem;
596
+ border: none;
597
+ background: transparent;
598
+ color: #888;
599
+ cursor: pointer;
600
+ border-radius: 6px;
418
601
  font-size: 0.875rem;
419
- font-weight: 600;
602
+ transition: all 0.2s;
603
+ }
604
+
605
+ .sdv-tab:hover {
606
+ background: #1f2b4d;
607
+ color: #ccc;
608
+ }
609
+
610
+ .sdv-tab.active {
611
+ background: #00d9ff20;
420
612
  color: #00d9ff;
421
613
  }
422
614
 
423
- .state-diff-viewer__content {
424
- padding: 1rem;
425
- max-height: 400px;
426
- overflow: auto;
615
+ .sdv-search {
616
+ padding: 0.5rem 1rem;
617
+ border: 1px solid #333;
618
+ border-radius: 6px;
619
+ background: #0f0f23;
620
+ color: #e0e0e0;
621
+ font-size: 0.875rem;
622
+ width: 200px;
427
623
  }
428
624
 
429
- .state-diff-viewer__summary {
430
- margin-top: 1rem;
625
+ .sdv-anomalies {
626
+ margin: 1rem;
431
627
  padding: 1rem;
432
- background: #16213e;
628
+ background: #ef444420;
629
+ border: 1px solid #ef4444;
433
630
  border-radius: 8px;
434
631
  }
435
632
 
436
- .state-diff-viewer__summary h4 {
437
- margin: 0 0 0.5rem 0;
438
- color: #00d9ff;
633
+ .sdv-anomaly-header {
634
+ display: flex;
635
+ align-items: center;
636
+ gap: 0.5rem;
637
+ font-weight: 600;
638
+ margin-bottom: 0.75rem;
639
+ color: #fca5a5;
439
640
  }
440
641
 
441
- .state-diff-viewer__changes {
442
- list-style: none;
443
- margin: 0;
444
- padding: 0;
642
+ .sdv-anomaly-icon {
643
+ font-size: 1.25rem;
445
644
  }
446
645
 
447
- .state-diff-viewer__change {
646
+ .sdv-anomaly-list {
448
647
  display: flex;
449
648
  flex-wrap: wrap;
649
+ gap: 0.5rem;
650
+ }
651
+
652
+ .sdv-anomaly-item {
653
+ display: flex;
450
654
  align-items: center;
451
655
  gap: 0.5rem;
452
- padding: 0.5rem;
656
+ padding: 0.375rem 0.75rem;
657
+ background: #0f0f23;
453
658
  border-radius: 4px;
454
- margin-bottom: 0.25rem;
659
+ font-size: 0.875rem;
455
660
  }
456
661
 
457
- .state-diff-viewer__change--add {
458
- background: rgba(34, 197, 94, 0.1);
459
- border-left: 3px solid #22c55e;
662
+ .sdv-anomaly-item code {
663
+ color: #f472b6;
460
664
  }
461
665
 
462
- .state-diff-viewer__change--update {
463
- background: rgba(251, 191, 36, 0.1);
464
- border-left: 3px solid #fbbf24;
666
+ .sdv-anomaly-badge {
667
+ font-size: 0.75rem;
668
+ padding: 0.125rem 0.375rem;
669
+ background: #ef444440;
670
+ border-radius: 3px;
465
671
  }
466
672
 
467
- .state-diff-viewer__change--remove {
468
- background: rgba(239, 68, 68, 0.1);
469
- border-left: 3px solid #ef4444;
673
+ .sdv-summary {
674
+ display: flex;
675
+ gap: 1.5rem;
676
+ padding: 0.75rem 1rem;
677
+ background: #0f0f23;
678
+ border-bottom: 1px solid #333;
679
+ }
680
+
681
+ .sdv-summary-item {
682
+ display: flex;
683
+ align-items: center;
684
+ gap: 0.5rem;
685
+ font-size: 0.875rem;
686
+ }
687
+
688
+ .sdv-dot {
689
+ width: 8px;
690
+ height: 8px;
691
+ border-radius: 50%;
692
+ }
693
+
694
+ .sdv-add .sdv-dot { background: #22c55e; }
695
+ .sdv-update .sdv-dot { background: #fbbf24; }
696
+ .sdv-remove .sdv-dot { background: #ef4444; }
697
+
698
+ .sdv-content {
699
+ max-height: 500px;
700
+ overflow: auto;
701
+ }
702
+
703
+ .sdv-empty {
704
+ padding: 2rem;
705
+ text-align: center;
706
+ color: #666;
707
+ }
708
+
709
+ .sdv-diff-list {
710
+ padding: 1rem;
711
+ }
712
+
713
+ .sdv-diff-item {
714
+ margin-bottom: 0.75rem;
715
+ padding: 1rem;
716
+ background: #16213e;
717
+ border-radius: 8px;
718
+ border-left: 4px solid;
719
+ }
720
+
721
+ .sdv-diff-add { border-left-color: #22c55e; }
722
+ .sdv-diff-update { border-left-color: #fbbf24; }
723
+ .sdv-diff-remove { border-left-color: #ef4444; }
724
+
725
+ .sdv-diff-header {
726
+ display: flex;
727
+ align-items: center;
728
+ gap: 0.75rem;
729
+ margin-bottom: 0.75rem;
470
730
  }
471
731
 
472
- .state-diff-viewer__change-type {
732
+ .sdv-diff-op {
473
733
  font-size: 0.7rem;
474
- padding: 0.125rem 0.375rem;
475
- border-radius: 3px;
476
- background: #333;
734
+ font-weight: 600;
735
+ padding: 0.25rem 0.5rem;
736
+ border-radius: 4px;
737
+ text-transform: uppercase;
738
+ }
739
+
740
+ .sdv-diff-add .sdv-diff-op { background: #22c55e30; color: #22c55e; }
741
+ .sdv-diff-update .sdv-diff-op { background: #fbbf2430; color: #fbbf24; }
742
+ .sdv-diff-remove .sdv-diff-op { background: #ef444430; color: #ef4444; }
743
+
744
+ .sdv-diff-path {
745
+ flex: 1;
746
+ font-family: 'Fira Code', monospace;
747
+ color: #a78bfa;
748
+ }
749
+
750
+ .sdv-copy-btn {
751
+ padding: 0.25rem 0.5rem;
752
+ border: none;
753
+ background: transparent;
477
754
  color: #888;
755
+ cursor: pointer;
756
+ border-radius: 4px;
757
+ font-size: 0.875rem;
478
758
  }
479
759
 
480
- .state-diff-viewer__value {
481
- padding: 0.125rem 0.375rem;
482
- border-radius: 3px;
760
+ .sdv-copy-btn:hover {
761
+ background: #333;
762
+ color: #fff;
763
+ }
764
+
765
+ .sdv-diff-values {
766
+ display: flex;
767
+ align-items: center;
768
+ gap: 0.75rem;
769
+ flex-wrap: wrap;
770
+ }
771
+
772
+ .sdv-value {
773
+ padding: 0.5rem 0.75rem;
774
+ border-radius: 6px;
775
+ font-family: 'Fira Code', monospace;
776
+ font-size: 0.875rem;
777
+ max-width: 300px;
778
+ overflow: auto;
779
+ }
780
+
781
+ .sdv-value-label {
782
+ display: block;
783
+ font-size: 0.7rem;
784
+ color: #666;
785
+ margin-bottom: 0.25rem;
786
+ font-family: system-ui;
483
787
  }
484
788
 
485
- .state-diff-viewer__value--old {
486
- background: rgba(239, 68, 68, 0.2);
789
+ .sdv-value-old {
790
+ background: #ef444420;
487
791
  color: #fca5a5;
488
- text-decoration: line-through;
489
792
  }
490
793
 
491
- .state-diff-viewer__value--new {
492
- background: rgba(34, 197, 94, 0.2);
794
+ .sdv-value-new {
795
+ background: #22c55e20;
493
796
  color: #86efac;
494
797
  }
495
798
 
496
- .state-diff-viewer__arrow {
799
+ .sdv-arrow {
497
800
  color: #666;
801
+ font-size: 1.25rem;
498
802
  }
499
- ` })
500
- ] });
501
- }
502
- function StateTree({
503
- value,
504
- path,
505
- changedPaths,
506
- defaultExpandDepth,
507
- depth = 0,
508
- changeType
509
- }) {
510
- const [isExpanded, setIsExpanded] = React2.useState(depth < defaultExpandDepth);
511
- const isChanged = path ? changedPaths.has(path) : false;
512
- const toggle = React2.useCallback(() => setIsExpanded((e) => !e), []);
513
- if (value === null) {
514
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__null", children: "null" });
515
- }
516
- if (value === void 0) {
517
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__undefined", children: "undefined" });
518
- }
519
- if (typeof value === "boolean") {
520
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__boolean", children: String(value) });
521
- }
522
- if (typeof value === "number") {
523
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `state-tree__number ${isChanged ? "state-tree--changed" : ""}`, children: String(value) });
524
- }
525
- if (typeof value === "string") {
526
- return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `state-tree__string ${isChanged ? "state-tree--changed" : ""}`, children: [
527
- '"',
528
- value,
529
- '"'
530
- ] });
531
- }
532
- if (Array.isArray(value)) {
533
- if (value.length === 0) {
534
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__empty", children: "[]" });
535
- }
536
- return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__array", children: [
537
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__toggle", onClick: toggle, children: [
538
- isExpanded ? "\u25BC" : "\u25B6",
539
- " Array(",
540
- value.length,
541
- ")"
542
- ] }),
543
- isExpanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "state-tree__children", children: value.map((item, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "state-tree__item", children: [
544
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__key", children: [
545
- index,
546
- ":"
547
- ] }),
548
- /* @__PURE__ */ jsxRuntime.jsx(
549
- StateTree,
550
- {
551
- value: item,
552
- path: path ? `${path}[${index}]` : `[${index}]`,
553
- changedPaths,
554
- defaultExpandDepth,
555
- depth: depth + 1,
556
- changeType
557
- }
558
- )
559
- ] }, index)) })
803
+
804
+ .sdv-state-view {
805
+ padding: 1rem;
806
+ }
807
+
808
+ .sdv-state-actions {
809
+ margin-bottom: 1rem;
810
+ }
811
+
812
+ .sdv-copy-full {
813
+ padding: 0.5rem 1rem;
814
+ border: 1px solid #333;
815
+ background: #16213e;
816
+ color: #e0e0e0;
817
+ border-radius: 6px;
818
+ cursor: pointer;
819
+ font-size: 0.875rem;
820
+ }
821
+
822
+ .sdv-copy-full:hover {
823
+ background: #1f2b4d;
824
+ }
825
+
826
+ .sdv-raw-view {
827
+ padding: 1rem;
828
+ }
829
+
830
+ .sdv-raw-panels {
831
+ display: grid;
832
+ grid-template-columns: 1fr 1fr;
833
+ gap: 1rem;
834
+ }
835
+
836
+ .sdv-raw-panel {
837
+ background: #0f0f23;
838
+ border-radius: 8px;
839
+ overflow: hidden;
840
+ }
841
+
842
+ .sdv-raw-panel h4 {
843
+ margin: 0;
844
+ padding: 0.75rem 1rem;
845
+ background: #16213e;
846
+ color: #00d9ff;
847
+ font-size: 0.875rem;
848
+ }
849
+
850
+ .sdv-raw-panel pre {
851
+ margin: 0;
852
+ padding: 1rem;
853
+ font-size: 0.8rem;
854
+ overflow: auto;
855
+ max-height: 300px;
856
+ }
857
+
858
+ /* State tree styles */
859
+ .state-tree__toggle {
860
+ cursor: pointer;
861
+ color: #888;
862
+ user-select: none;
863
+ }
864
+
865
+ .state-tree__toggle:hover {
866
+ color: #00d9ff;
867
+ }
868
+
869
+ .state-tree__children {
870
+ margin-left: 1.5rem;
871
+ border-left: 1px solid #333;
872
+ padding-left: 0.75rem;
873
+ }
874
+
875
+ .state-tree__item {
876
+ padding: 0.25rem 0;
877
+ }
878
+
879
+ .state-tree__key {
880
+ color: #a78bfa;
881
+ margin-right: 0.5rem;
882
+ }
883
+
884
+ .state-tree__null { color: #f472b6; font-style: italic; }
885
+ .state-tree__undefined { color: #ef4444; font-style: italic; }
886
+ .state-tree__boolean { color: #fb923c; }
887
+ .state-tree__number { color: #22c55e; }
888
+ .state-tree__string { color: #fbbf24; }
889
+ .state-tree__empty { color: #666; }
890
+ .state-tree--changed {
891
+ background: #fbbf2430;
892
+ padding: 0 0.25rem;
893
+ border-radius: 3px;
894
+ }
895
+
896
+ .state-tree__anomaly {
897
+ display: inline-block;
898
+ margin-left: 0.5rem;
899
+ padding: 0.125rem 0.375rem;
900
+ background: #ef444440;
901
+ border-radius: 3px;
902
+ font-size: 0.7rem;
903
+ color: #fca5a5;
904
+ }
905
+ ` })
906
+ ] });
907
+ }
908
+ function ValueDisplay({ value }) {
909
+ if (value === null) {
910
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__null", children: "null" });
911
+ }
912
+ if (value === void 0) {
913
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__undefined", children: [
914
+ "undefined",
915
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__anomaly", children: "\u26A0\uFE0F" })
916
+ ] });
917
+ }
918
+ if (typeof value === "boolean") {
919
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__boolean", children: String(value) });
920
+ }
921
+ if (typeof value === "number") {
922
+ if (isNaN(value)) {
923
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__undefined", children: [
924
+ "NaN",
925
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__anomaly", children: "\u26A0\uFE0F Bug!" })
926
+ ] });
927
+ }
928
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__number", children: String(value) });
929
+ }
930
+ if (typeof value === "string") {
931
+ if (value === "") {
932
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__empty", children: '""' });
933
+ }
934
+ const display = value.length > 50 ? value.slice(0, 50) + "..." : value;
935
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__string", children: [
936
+ '"',
937
+ display,
938
+ '"'
939
+ ] });
940
+ }
941
+ if (Array.isArray(value)) {
942
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__empty", children: [
943
+ "[",
944
+ value.length,
945
+ " items]"
560
946
  ] });
561
947
  }
562
948
  if (typeof value === "object") {
563
- const entries = Object.entries(value);
564
- if (entries.length === 0) {
565
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__empty", children: "{}" });
949
+ const keys = Object.keys(value);
950
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__empty", children: "{" + keys.length + " keys}" });
951
+ }
952
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { children: String(value) });
953
+ }
954
+ function StateTree({
955
+ value,
956
+ path,
957
+ changedPaths,
958
+ defaultExpandDepth,
959
+ depth = 0,
960
+ changeType
961
+ }) {
962
+ const [isExpanded, setIsExpanded] = React4.useState(depth < defaultExpandDepth);
963
+ const isChanged = path ? changedPaths.has(path) : false;
964
+ const toggle = React4.useCallback(() => setIsExpanded((e) => !e), []);
965
+ if (value === null) {
966
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__null", children: "null" });
967
+ }
968
+ if (value === void 0) {
969
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__undefined", children: [
970
+ "undefined",
971
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__anomaly", children: "\u26A0\uFE0F" })
972
+ ] });
973
+ }
974
+ if (typeof value === "boolean") {
975
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__boolean", children: String(value) });
976
+ }
977
+ if (typeof value === "number") {
978
+ if (isNaN(value)) {
979
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `state-tree__undefined ${isChanged ? "state-tree--changed" : ""}`, children: [
980
+ "NaN",
981
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__anomaly", children: "\u26A0\uFE0F BUG!" })
982
+ ] });
566
983
  }
567
- return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__object", children: [
984
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `state-tree__number ${isChanged ? "state-tree--changed" : ""}`, children: String(value) });
985
+ }
986
+ if (typeof value === "string") {
987
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `state-tree__string ${isChanged ? "state-tree--changed" : ""}`, children: [
988
+ '"',
989
+ value,
990
+ '"',
991
+ value === "" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__anomaly", children: "empty" })
992
+ ] });
993
+ }
994
+ if (Array.isArray(value)) {
995
+ if (value.length === 0) {
996
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__empty", children: "[]" });
997
+ }
998
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__array", children: [
568
999
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__toggle", onClick: toggle, children: [
569
1000
  isExpanded ? "\u25BC" : "\u25B6",
570
- " Object(",
571
- entries.length,
1001
+ " Array(",
1002
+ value.length,
572
1003
  ")"
573
1004
  ] }),
574
- isExpanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "state-tree__children", children: entries.map(([key, val]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "state-tree__item", children: [
1005
+ isExpanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "state-tree__children", children: value.map((item, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "state-tree__item", children: [
575
1006
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__key", children: [
576
- key,
1007
+ index,
577
1008
  ":"
578
1009
  ] }),
579
1010
  /* @__PURE__ */ jsxRuntime.jsx(
580
1011
  StateTree,
581
1012
  {
582
- value: val,
583
- path: path ? `${path}.${key}` : key,
1013
+ value: item,
1014
+ path: path ? `${path}[${index}]` : `[${index}]`,
584
1015
  changedPaths,
585
1016
  defaultExpandDepth,
586
1017
  depth: depth + 1,
587
1018
  changeType
588
1019
  }
589
1020
  )
590
- ] }, key)) })
1021
+ ] }, index)) })
591
1022
  ] });
592
1023
  }
593
- return /* @__PURE__ */ jsxRuntime.jsx("span", { children: String(value) });
594
- }
595
- function formatValue(value) {
596
- if (value === void 0) return "undefined";
597
- if (value === null) return "null";
598
1024
  if (typeof value === "object") {
599
- try {
600
- const str = JSON.stringify(value);
601
- return str.length > 50 ? str.slice(0, 50) + "..." : str;
602
- } catch {
603
- return "[Object]";
1025
+ const entries = Object.entries(value);
1026
+ if (entries.length === 0) {
1027
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__empty", children: "{}" });
604
1028
  }
1029
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__object", children: [
1030
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__toggle", onClick: toggle, children: [
1031
+ isExpanded ? "\u25BC" : "\u25B6",
1032
+ " Object(",
1033
+ entries.length,
1034
+ ")"
1035
+ ] }),
1036
+ isExpanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "state-tree__children", children: entries.map(([key, val]) => {
1037
+ const childPath = path ? `${path}.${key}` : key;
1038
+ const childChanged = changedPaths.has(childPath);
1039
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "state-tree__item", children: [
1040
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `state-tree__key ${childChanged ? "state-tree--changed" : ""}`, children: [
1041
+ key,
1042
+ ":"
1043
+ ] }),
1044
+ /* @__PURE__ */ jsxRuntime.jsx(
1045
+ StateTree,
1046
+ {
1047
+ value: val,
1048
+ path: childPath,
1049
+ changedPaths,
1050
+ defaultExpandDepth,
1051
+ depth: depth + 1,
1052
+ changeType
1053
+ }
1054
+ )
1055
+ ] }, key);
1056
+ }) })
1057
+ ] });
605
1058
  }
606
- return String(value);
1059
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { children: String(value) });
607
1060
  }
608
1061
  var sourceColors = {
609
1062
  react: "#61dafb",
@@ -614,6 +1067,15 @@ var sourceColors = {
614
1067
  api: "#3b82f6",
615
1068
  custom: "#6b7280"
616
1069
  };
1070
+ var sourceEmojis = {
1071
+ react: "\u269B\uFE0F",
1072
+ redux: "\u{1F504}",
1073
+ zustand: "\u{1F43B}",
1074
+ express: "\u{1F682}",
1075
+ websocket: "\u{1F50C}",
1076
+ api: "\u{1F310}",
1077
+ custom: "\u2699\uFE0F"
1078
+ };
617
1079
  function TimelineScrubber({
618
1080
  mutations,
619
1081
  selectedIndex,
@@ -623,9 +1085,13 @@ function TimelineScrubber({
623
1085
  onPause,
624
1086
  onStepForward,
625
1087
  onStepBackward,
1088
+ playbackSpeed = 1,
1089
+ onSpeedChange,
626
1090
  className = ""
627
1091
  }) {
628
- const handleSliderChange = React2.useCallback(
1092
+ const [filter, setFilter] = React4.useState("all");
1093
+ const [hoverIndex, setHoverIndex] = React4.useState(null);
1094
+ const handleSliderChange = React4.useCallback(
629
1095
  (e) => {
630
1096
  const index = parseInt(e.target.value, 10);
631
1097
  if (mutations[index]) {
@@ -634,7 +1100,7 @@ function TimelineScrubber({
634
1100
  },
635
1101
  [mutations, onSelect]
636
1102
  );
637
- const handleMarkerClick = React2.useCallback(
1103
+ const handleMarkerClick = React4.useCallback(
638
1104
  (index) => {
639
1105
  if (mutations[index]) {
640
1106
  onSelect(index, mutations[index]);
@@ -642,54 +1108,128 @@ function TimelineScrubber({
642
1108
  },
643
1109
  [mutations, onSelect]
644
1110
  );
1111
+ const sourceStats = React4.useMemo(() => {
1112
+ const stats = {};
1113
+ for (const m of mutations) {
1114
+ stats[m.source] = (stats[m.source] || 0) + 1;
1115
+ }
1116
+ return stats;
1117
+ }, [mutations]);
1118
+ React4.useMemo(() => {
1119
+ if (filter === "all") return mutations;
1120
+ return mutations.filter((m) => m.source === filter);
1121
+ }, [mutations, filter]);
1122
+ const anomalyCount = React4.useMemo(() => {
1123
+ let count = 0;
1124
+ for (const m of mutations) {
1125
+ if (m.diff) {
1126
+ for (const d of m.diff) {
1127
+ if (d.newValue === void 0 || typeof d.newValue === "number" && isNaN(d.newValue)) {
1128
+ count++;
1129
+ break;
1130
+ }
1131
+ }
1132
+ }
1133
+ }
1134
+ return count;
1135
+ }, [mutations]);
645
1136
  const formatTime = (timestamp) => {
646
1137
  const date = new Date(timestamp);
647
1138
  return date.toLocaleTimeString("en-US", {
648
1139
  hour12: false,
649
1140
  hour: "2-digit",
650
1141
  minute: "2-digit",
651
- second: "2-digit",
652
- fractionalSecondDigits: 3
1142
+ second: "2-digit"
653
1143
  });
654
1144
  };
1145
+ const formatDuration = (ms) => {
1146
+ if (!ms) return "";
1147
+ if (ms < 1) return `${(ms * 1e3).toFixed(0)}\u03BCs`;
1148
+ if (ms < 1e3) return `${ms.toFixed(0)}ms`;
1149
+ return `${(ms / 1e3).toFixed(1)}s`;
1150
+ };
655
1151
  if (mutations.length === 0) {
656
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `timeline-scrubber timeline-scrubber--empty ${className}`, children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "No mutations recorded yet" }) });
1152
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `timeline-scrubber timeline-scrubber--empty ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-empty-state", children: [
1153
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-empty-icon", children: "\u{1F4ED}" }),
1154
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "No Mutations Yet" }),
1155
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Start interacting with your app to capture state changes" })
1156
+ ] }) });
657
1157
  }
658
1158
  const selectedMutation = mutations[selectedIndex];
1159
+ const previewMutation = hoverIndex !== null ? mutations[hoverIndex] : selectedMutation;
659
1160
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `timeline-scrubber ${className}`, children: [
660
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "timeline-scrubber__header", children: [
661
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "timeline-scrubber__count", children: [
662
- selectedIndex + 1,
663
- " / ",
664
- mutations.length,
665
- " mutations"
1161
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-stats", children: [
1162
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-stat-main", children: [
1163
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-stat-value", children: mutations.length }),
1164
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-stat-label", children: "mutations" })
1165
+ ] }),
1166
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ts-stat-sources", children: Object.entries(sourceStats).map(([source, count]) => /* @__PURE__ */ jsxRuntime.jsxs(
1167
+ "button",
1168
+ {
1169
+ className: `ts-source-badge ${filter === source ? "active" : ""}`,
1170
+ style: { "--source-color": sourceColors[source] },
1171
+ onClick: () => setFilter(filter === source ? "all" : source),
1172
+ title: `${source}: ${count} mutations`,
1173
+ children: [
1174
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-source-emoji", children: sourceEmojis[source] }),
1175
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-source-count", children: count })
1176
+ ]
1177
+ },
1178
+ source
1179
+ )) }),
1180
+ anomalyCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-anomaly-badge", children: [
1181
+ "\u26A0\uFE0F ",
1182
+ anomalyCount,
1183
+ " issue",
1184
+ anomalyCount > 1 ? "s" : ""
666
1185
  ] }),
667
- selectedMutation && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "timeline-scrubber__time", children: formatTime(selectedMutation.timestamp) })
1186
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-position", children: [
1187
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-current", children: selectedIndex + 1 }),
1188
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-separator", children: "/" }),
1189
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-total", children: mutations.length })
1190
+ ] })
668
1191
  ] }),
669
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "timeline-scrubber__track", children: [
670
- mutations.map((mutation, index) => /* @__PURE__ */ jsxRuntime.jsx(
1192
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ts-chart", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-chart-track", children: [
1193
+ mutations.map((mutation, index) => {
1194
+ const isSelected = index === selectedIndex;
1195
+ const isHovered = index === hoverIndex;
1196
+ const hasAnomaly = mutation.diff?.some(
1197
+ (d) => d.newValue === void 0 || typeof d.newValue === "number" && isNaN(d.newValue)
1198
+ );
1199
+ return /* @__PURE__ */ jsxRuntime.jsx(
1200
+ "div",
1201
+ {
1202
+ className: `ts-marker ${isSelected ? "selected" : ""} ${isHovered ? "hovered" : ""} ${hasAnomaly ? "anomaly" : ""}`,
1203
+ style: {
1204
+ left: `${index / Math.max(mutations.length - 1, 1) * 100}%`,
1205
+ backgroundColor: sourceColors[mutation.source] || sourceColors.custom
1206
+ },
1207
+ onClick: () => handleMarkerClick(index),
1208
+ onMouseEnter: () => setHoverIndex(index),
1209
+ onMouseLeave: () => setHoverIndex(null)
1210
+ },
1211
+ mutation.id
1212
+ );
1213
+ }),
1214
+ /* @__PURE__ */ jsxRuntime.jsx(
671
1215
  "div",
672
1216
  {
673
- className: `timeline-scrubber__marker ${index === selectedIndex ? "timeline-scrubber__marker--selected" : ""}`,
1217
+ className: "ts-progress",
674
1218
  style: {
675
- left: `${index / (mutations.length - 1 || 1) * 100}%`,
676
- backgroundColor: sourceColors[mutation.source] || sourceColors.custom
677
- },
678
- onClick: () => handleMarkerClick(index),
679
- title: `${mutation.source}: ${mutation.actionType}${mutation.component ? ` @ ${mutation.component}` : ""}`
680
- },
681
- mutation.id
682
- )),
1219
+ width: `${selectedIndex / Math.max(mutations.length - 1, 1) * 100}%`
1220
+ }
1221
+ }
1222
+ ),
683
1223
  /* @__PURE__ */ jsxRuntime.jsx(
684
1224
  "div",
685
1225
  {
686
- className: "timeline-scrubber__position",
1226
+ className: "ts-cursor",
687
1227
  style: {
688
- left: `${selectedIndex / (mutations.length - 1 || 1) * 100}%`
1228
+ left: `${selectedIndex / Math.max(mutations.length - 1, 1) * 100}%`
689
1229
  }
690
1230
  }
691
1231
  )
692
- ] }),
1232
+ ] }) }),
693
1233
  /* @__PURE__ */ jsxRuntime.jsx(
694
1234
  "input",
695
1235
  {
@@ -698,199 +1238,2123 @@ function TimelineScrubber({
698
1238
  max: mutations.length - 1,
699
1239
  value: selectedIndex,
700
1240
  onChange: handleSliderChange,
701
- className: "timeline-scrubber__slider"
1241
+ className: "ts-slider"
702
1242
  }
703
1243
  ),
704
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "timeline-scrubber__controls", children: [
705
- /* @__PURE__ */ jsxRuntime.jsx(
706
- "button",
707
- {
708
- onClick: onStepBackward,
709
- disabled: selectedIndex === 0,
710
- className: "timeline-scrubber__button",
711
- title: "Step Backward",
712
- children: "\u23EE"
713
- }
714
- ),
715
- isPlaying ? /* @__PURE__ */ jsxRuntime.jsx(
716
- "button",
717
- {
718
- onClick: onPause,
719
- className: "timeline-scrubber__button timeline-scrubber__button--primary",
720
- title: "Pause",
721
- children: "\u23F8"
722
- }
723
- ) : /* @__PURE__ */ jsxRuntime.jsx(
724
- "button",
725
- {
726
- onClick: onPlay,
727
- disabled: selectedIndex === mutations.length - 1,
728
- className: "timeline-scrubber__button timeline-scrubber__button--primary",
729
- title: "Play",
730
- children: "\u25B6"
731
- }
732
- ),
733
- /* @__PURE__ */ jsxRuntime.jsx(
734
- "button",
735
- {
736
- onClick: onStepForward,
737
- disabled: selectedIndex === mutations.length - 1,
738
- className: "timeline-scrubber__button",
739
- title: "Step Forward",
740
- children: "\u23ED"
741
- }
742
- )
1244
+ previewMutation && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-preview", children: [
1245
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-preview-header", children: [
1246
+ /* @__PURE__ */ jsxRuntime.jsxs(
1247
+ "span",
1248
+ {
1249
+ className: "ts-preview-source",
1250
+ style: { backgroundColor: sourceColors[previewMutation.source] },
1251
+ children: [
1252
+ sourceEmojis[previewMutation.source],
1253
+ " ",
1254
+ previewMutation.source
1255
+ ]
1256
+ }
1257
+ ),
1258
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-preview-action", children: previewMutation.actionType }),
1259
+ previewMutation.duration && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-preview-duration", children: formatDuration(previewMutation.duration) })
1260
+ ] }),
1261
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-preview-details", children: [
1262
+ previewMutation.component && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-preview-component", children: previewMutation.component }),
1263
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-preview-time", children: formatTime(previewMutation.timestamp) })
1264
+ ] }),
1265
+ previewMutation.diff && previewMutation.diff.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-preview-changes", children: [
1266
+ previewMutation.diff.slice(0, 3).map((d, i) => /* @__PURE__ */ jsxRuntime.jsx("code", { className: `ts-preview-path ts-preview-${d.operation.toLowerCase()}`, children: d.path }, i)),
1267
+ previewMutation.diff.length > 3 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ts-preview-more", children: [
1268
+ "+",
1269
+ previewMutation.diff.length - 3,
1270
+ " more"
1271
+ ] })
1272
+ ] })
1273
+ ] }),
1274
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-controls", children: [
1275
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-control-group", children: [
1276
+ /* @__PURE__ */ jsxRuntime.jsx(
1277
+ "button",
1278
+ {
1279
+ onClick: onStepBackward,
1280
+ disabled: selectedIndex === 0,
1281
+ className: "ts-btn",
1282
+ title: "Previous mutation (\u2190)",
1283
+ children: "\u23EE\uFE0F"
1284
+ }
1285
+ ),
1286
+ isPlaying ? /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onPause, className: "ts-btn ts-btn-primary", title: "Pause", children: "\u23F8\uFE0F" }) : /* @__PURE__ */ jsxRuntime.jsx(
1287
+ "button",
1288
+ {
1289
+ onClick: onPlay,
1290
+ disabled: selectedIndex === mutations.length - 1,
1291
+ className: "ts-btn ts-btn-primary",
1292
+ title: "Play through timeline",
1293
+ children: "\u25B6\uFE0F"
1294
+ }
1295
+ ),
1296
+ /* @__PURE__ */ jsxRuntime.jsx(
1297
+ "button",
1298
+ {
1299
+ onClick: onStepForward,
1300
+ disabled: selectedIndex === mutations.length - 1,
1301
+ className: "ts-btn",
1302
+ title: "Next mutation (\u2192)",
1303
+ children: "\u23ED\uFE0F"
1304
+ }
1305
+ )
1306
+ ] }),
1307
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-control-group", children: [
1308
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "ts-speed-label", children: "Speed:" }),
1309
+ /* @__PURE__ */ jsxRuntime.jsxs(
1310
+ "select",
1311
+ {
1312
+ value: playbackSpeed,
1313
+ onChange: (e) => onSpeedChange?.(Number(e.target.value)),
1314
+ className: "ts-speed-select",
1315
+ children: [
1316
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: 0.5, children: "0.5x" }),
1317
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: 1, children: "1x" }),
1318
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: 2, children: "2x" }),
1319
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: 4, children: "4x" })
1320
+ ]
1321
+ }
1322
+ )
1323
+ ] }),
1324
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-control-group", children: [
1325
+ /* @__PURE__ */ jsxRuntime.jsx(
1326
+ "button",
1327
+ {
1328
+ className: "ts-btn ts-btn-text",
1329
+ onClick: () => onSelect(0, mutations[0]),
1330
+ disabled: selectedIndex === 0,
1331
+ children: "\u23EA Start"
1332
+ }
1333
+ ),
1334
+ /* @__PURE__ */ jsxRuntime.jsx(
1335
+ "button",
1336
+ {
1337
+ className: "ts-btn ts-btn-text",
1338
+ onClick: () => onSelect(mutations.length - 1, mutations[mutations.length - 1]),
1339
+ disabled: selectedIndex === mutations.length - 1,
1340
+ children: "End \u23E9"
1341
+ }
1342
+ )
1343
+ ] })
743
1344
  ] }),
744
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "timeline-scrubber__legend", children: Object.entries(sourceColors).map(([source, color]) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "timeline-scrubber__legend-item", children: [
745
- /* @__PURE__ */ jsxRuntime.jsx(
746
- "span",
747
- {
748
- className: "timeline-scrubber__legend-dot",
749
- style: { backgroundColor: color }
750
- }
751
- ),
752
- source
753
- ] }, source)) }),
754
1345
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
755
1346
  .timeline-scrubber {
756
- padding: 1rem;
757
1347
  background: #1a1a2e;
758
- border-radius: 8px;
1348
+ border-radius: 12px;
1349
+ padding: 1.25rem;
759
1350
  color: #fff;
760
1351
  }
761
1352
 
762
1353
  .timeline-scrubber--empty {
1354
+ padding: 3rem;
1355
+ }
1356
+
1357
+ .ts-empty-state {
763
1358
  text-align: center;
764
- color: #888;
765
1359
  }
766
1360
 
767
- .timeline-scrubber__header {
768
- display: flex;
769
- justify-content: space-between;
770
- margin-bottom: 0.5rem;
1361
+ .ts-empty-icon {
1362
+ font-size: 3rem;
1363
+ display: block;
1364
+ margin-bottom: 1rem;
1365
+ }
1366
+
1367
+ .ts-empty-state h3 {
1368
+ margin: 0 0 0.5rem;
1369
+ color: #888;
1370
+ }
1371
+
1372
+ .ts-empty-state p {
1373
+ margin: 0;
1374
+ color: #666;
771
1375
  font-size: 0.875rem;
772
1376
  }
773
1377
 
774
- .timeline-scrubber__count {
1378
+ .ts-stats {
1379
+ display: flex;
1380
+ align-items: center;
1381
+ gap: 1.5rem;
1382
+ margin-bottom: 1rem;
1383
+ flex-wrap: wrap;
1384
+ }
1385
+
1386
+ .ts-stat-main {
1387
+ display: flex;
1388
+ align-items: baseline;
1389
+ gap: 0.5rem;
1390
+ }
1391
+
1392
+ .ts-stat-value {
1393
+ font-size: 1.5rem;
1394
+ font-weight: 700;
775
1395
  color: #00d9ff;
776
- font-weight: 600;
777
1396
  }
778
1397
 
779
- .timeline-scrubber__time {
1398
+ .ts-stat-label {
1399
+ font-size: 0.875rem;
780
1400
  color: #888;
781
- font-family: monospace;
782
1401
  }
783
1402
 
784
- .timeline-scrubber__track {
1403
+ .ts-stat-sources {
1404
+ display: flex;
1405
+ gap: 0.5rem;
1406
+ }
1407
+
1408
+ .ts-source-badge {
1409
+ display: flex;
1410
+ align-items: center;
1411
+ gap: 0.25rem;
1412
+ padding: 0.375rem 0.625rem;
1413
+ border: 1px solid transparent;
1414
+ border-radius: 20px;
1415
+ background: var(--source-color, #666)20;
1416
+ color: var(--source-color, #888);
1417
+ cursor: pointer;
1418
+ font-size: 0.75rem;
1419
+ transition: all 0.2s;
1420
+ }
1421
+
1422
+ .ts-source-badge:hover, .ts-source-badge.active {
1423
+ background: var(--source-color, #666)40;
1424
+ border-color: var(--source-color, #666);
1425
+ }
1426
+
1427
+ .ts-source-emoji {
1428
+ font-size: 0.875rem;
1429
+ }
1430
+
1431
+ .ts-anomaly-badge {
1432
+ padding: 0.375rem 0.75rem;
1433
+ background: #ef444430;
1434
+ color: #fca5a5;
1435
+ border-radius: 20px;
1436
+ font-size: 0.75rem;
1437
+ font-weight: 600;
1438
+ }
1439
+
1440
+ .ts-position {
1441
+ margin-left: auto;
1442
+ font-family: 'Fira Code', monospace;
1443
+ font-size: 0.875rem;
1444
+ }
1445
+
1446
+ .ts-current {
1447
+ color: #00d9ff;
1448
+ font-weight: 600;
1449
+ }
1450
+
1451
+ .ts-separator {
1452
+ color: #444;
1453
+ margin: 0 0.25rem;
1454
+ }
1455
+
1456
+ .ts-total {
1457
+ color: #666;
1458
+ }
1459
+
1460
+ .ts-chart {
1461
+ margin-bottom: 0.5rem;
1462
+ }
1463
+
1464
+ .ts-chart-track {
785
1465
  position: relative;
786
- height: 24px;
1466
+ height: 32px;
787
1467
  background: #16213e;
788
- border-radius: 4px;
1468
+ border-radius: 8px;
1469
+ overflow: hidden;
1470
+ }
1471
+
1472
+ .ts-progress {
1473
+ position: absolute;
1474
+ top: 0;
1475
+ left: 0;
1476
+ height: 100%;
1477
+ background: linear-gradient(90deg, #00d9ff20, #00d9ff10);
1478
+ pointer-events: none;
1479
+ }
1480
+
1481
+ .ts-cursor {
1482
+ position: absolute;
1483
+ top: 0;
1484
+ width: 3px;
1485
+ height: 100%;
1486
+ background: #00d9ff;
1487
+ border-radius: 1.5px;
1488
+ pointer-events: none;
1489
+ box-shadow: 0 0 10px #00d9ff80;
1490
+ }
1491
+
1492
+ .ts-marker {
1493
+ position: absolute;
1494
+ width: 6px;
1495
+ height: 16px;
1496
+ top: 8px;
1497
+ margin-left: -3px;
1498
+ border-radius: 3px;
1499
+ cursor: pointer;
1500
+ opacity: 0.6;
1501
+ transition: all 0.15s;
1502
+ }
1503
+
1504
+ .ts-marker:hover, .ts-marker.hovered {
1505
+ opacity: 1;
1506
+ transform: scaleY(1.3);
1507
+ }
1508
+
1509
+ .ts-marker.selected {
1510
+ opacity: 1;
1511
+ box-shadow: 0 0 8px currentColor;
1512
+ transform: scaleY(1.4);
1513
+ }
1514
+
1515
+ .ts-marker.anomaly {
1516
+ border: 2px solid #ef4444;
1517
+ }
1518
+
1519
+ .ts-slider {
1520
+ width: 100%;
1521
+ height: 4px;
1522
+ margin: 0.5rem 0;
1523
+ cursor: pointer;
1524
+ -webkit-appearance: none;
1525
+ background: transparent;
1526
+ }
1527
+
1528
+ .ts-slider::-webkit-slider-runnable-track {
1529
+ height: 4px;
1530
+ background: #333;
1531
+ border-radius: 2px;
1532
+ }
1533
+
1534
+ .ts-slider::-webkit-slider-thumb {
1535
+ -webkit-appearance: none;
1536
+ width: 16px;
1537
+ height: 16px;
1538
+ margin-top: -6px;
1539
+ background: #00d9ff;
1540
+ border-radius: 50%;
1541
+ cursor: pointer;
1542
+ }
1543
+
1544
+ .ts-preview {
1545
+ margin: 1rem 0;
1546
+ padding: 1rem;
1547
+ background: #16213e;
1548
+ border-radius: 8px;
1549
+ border-left: 3px solid #00d9ff;
1550
+ }
1551
+
1552
+ .ts-preview-header {
1553
+ display: flex;
1554
+ align-items: center;
1555
+ gap: 0.75rem;
789
1556
  margin-bottom: 0.5rem;
790
1557
  }
791
1558
 
792
- .timeline-scrubber__marker {
793
- position: absolute;
794
- width: 4px;
795
- height: 16px;
796
- top: 4px;
797
- border-radius: 2px;
798
- cursor: pointer;
799
- transition: transform 0.1s, opacity 0.1s;
800
- opacity: 0.7;
801
- }
1559
+ .ts-preview-source {
1560
+ padding: 0.25rem 0.5rem;
1561
+ border-radius: 4px;
1562
+ font-size: 0.75rem;
1563
+ font-weight: 600;
1564
+ color: #000;
1565
+ }
1566
+
1567
+ .ts-preview-action {
1568
+ font-weight: 600;
1569
+ color: #e0e0e0;
1570
+ }
1571
+
1572
+ .ts-preview-duration {
1573
+ margin-left: auto;
1574
+ font-size: 0.75rem;
1575
+ color: #888;
1576
+ font-family: 'Fira Code', monospace;
1577
+ }
1578
+
1579
+ .ts-preview-details {
1580
+ display: flex;
1581
+ gap: 1rem;
1582
+ font-size: 0.875rem;
1583
+ color: #888;
1584
+ }
1585
+
1586
+ .ts-preview-component {
1587
+ color: #a78bfa;
1588
+ }
1589
+
1590
+ .ts-preview-changes {
1591
+ display: flex;
1592
+ flex-wrap: wrap;
1593
+ gap: 0.5rem;
1594
+ margin-top: 0.75rem;
1595
+ }
1596
+
1597
+ .ts-preview-path {
1598
+ padding: 0.25rem 0.5rem;
1599
+ border-radius: 4px;
1600
+ font-size: 0.75rem;
1601
+ }
1602
+
1603
+ .ts-preview-add { background: #22c55e20; color: #86efac; }
1604
+ .ts-preview-update { background: #fbbf2420; color: #fcd34d; }
1605
+ .ts-preview-remove { background: #ef444420; color: #fca5a5; }
1606
+
1607
+ .ts-preview-more {
1608
+ font-size: 0.75rem;
1609
+ color: #666;
1610
+ padding: 0.25rem;
1611
+ }
1612
+
1613
+ .ts-controls {
1614
+ display: flex;
1615
+ justify-content: center;
1616
+ align-items: center;
1617
+ gap: 1.5rem;
1618
+ padding-top: 0.5rem;
1619
+ flex-wrap: wrap;
1620
+ }
1621
+
1622
+ .ts-control-group {
1623
+ display: flex;
1624
+ align-items: center;
1625
+ gap: 0.5rem;
1626
+ }
1627
+
1628
+ .ts-btn {
1629
+ padding: 0.5rem 0.75rem;
1630
+ border: none;
1631
+ border-radius: 8px;
1632
+ background: #16213e;
1633
+ color: #fff;
1634
+ cursor: pointer;
1635
+ font-size: 1rem;
1636
+ transition: all 0.2s;
1637
+ }
1638
+
1639
+ .ts-btn:hover:not(:disabled) {
1640
+ background: #1f2b4d;
1641
+ }
1642
+
1643
+ .ts-btn:disabled {
1644
+ opacity: 0.4;
1645
+ cursor: not-allowed;
1646
+ }
1647
+
1648
+ .ts-btn-primary {
1649
+ background: #00d9ff;
1650
+ color: #000;
1651
+ }
1652
+
1653
+ .ts-btn-primary:hover:not(:disabled) {
1654
+ background: #00b8e6;
1655
+ }
1656
+
1657
+ .ts-btn-text {
1658
+ background: transparent;
1659
+ color: #888;
1660
+ font-size: 0.875rem;
1661
+ }
1662
+
1663
+ .ts-btn-text:hover:not(:disabled) {
1664
+ color: #fff;
1665
+ background: transparent;
1666
+ }
1667
+
1668
+ .ts-speed-label {
1669
+ font-size: 0.875rem;
1670
+ color: #888;
1671
+ }
1672
+
1673
+ .ts-speed-select {
1674
+ padding: 0.375rem 0.5rem;
1675
+ border: 1px solid #333;
1676
+ border-radius: 6px;
1677
+ background: #16213e;
1678
+ color: #e0e0e0;
1679
+ font-size: 0.875rem;
1680
+ cursor: pointer;
1681
+ }
1682
+ ` })
1683
+ ] });
1684
+ }
1685
+
1686
+ // src/core/analyzer.ts
1687
+ var StateAnalyzer = class {
1688
+ constructor() {
1689
+ this.invariants = [];
1690
+ this.updateTimestamps = /* @__PURE__ */ new Map();
1691
+ }
1692
+ /**
1693
+ * Register custom invariant rules
1694
+ */
1695
+ addInvariant(invariant) {
1696
+ this.invariants.push(invariant);
1697
+ }
1698
+ /**
1699
+ * Analyze a single mutation for issues
1700
+ */
1701
+ analyzeMutation(mutation, index, timeline) {
1702
+ const issues = [];
1703
+ if (mutation.diff) {
1704
+ for (const diff of mutation.diff) {
1705
+ if (diff.operation === "REMOVE") {
1706
+ issues.push({
1707
+ id: `issue_${mutation.id}_${diff.path}_loss`,
1708
+ category: "state-loss",
1709
+ severity: "critical",
1710
+ title: `State field removed: ${diff.path}`,
1711
+ description: `The field "${diff.path}" was removed from state. This may indicate an overwrite instead of a merge.`,
1712
+ mutationId: mutation.id,
1713
+ mutationIndex: index,
1714
+ path: diff.path,
1715
+ previousValue: diff.oldValue,
1716
+ currentValue: void 0,
1717
+ suggestion: "Use spread operator to preserve existing fields: setState(prev => ({ ...prev, newField }))",
1718
+ timestamp: mutation.timestamp
1719
+ });
1720
+ }
1721
+ if (diff.operation !== "REMOVE") {
1722
+ const invalidIssue = this.checkInvalidValue(diff, mutation, index);
1723
+ if (invalidIssue) {
1724
+ issues.push(invalidIssue);
1725
+ }
1726
+ }
1727
+ if (diff.operation === "UPDATE") {
1728
+ const typeIssue = this.checkTypeChange(diff, mutation, index);
1729
+ if (typeIssue) {
1730
+ issues.push(typeIssue);
1731
+ }
1732
+ }
1733
+ }
1734
+ }
1735
+ if (this.isNoOpUpdate(mutation)) {
1736
+ issues.push({
1737
+ id: `issue_${mutation.id}_noop`,
1738
+ category: "no-op-update",
1739
+ severity: "info",
1740
+ title: "Redundant state update",
1741
+ description: "This mutation did not change any values. Consider memoizing or adding conditions.",
1742
+ mutationId: mutation.id,
1743
+ mutationIndex: index,
1744
+ suggestion: "Add a condition before updating: if (newValue !== currentValue) setState(newValue)",
1745
+ timestamp: mutation.timestamp
1746
+ });
1747
+ }
1748
+ const excessiveIssue = this.checkExcessiveUpdates(mutation, index);
1749
+ if (excessiveIssue) {
1750
+ issues.push(excessiveIssue);
1751
+ }
1752
+ for (const invariant of this.invariants) {
1753
+ const violation = this.checkInvariant(invariant, mutation, index);
1754
+ if (violation) {
1755
+ issues.push(violation);
1756
+ }
1757
+ }
1758
+ return issues;
1759
+ }
1760
+ /**
1761
+ * Check for invalid values (NaN, unexpected undefined/null)
1762
+ */
1763
+ checkInvalidValue(diff, mutation, index) {
1764
+ const value = diff.newValue;
1765
+ if (typeof value === "number" && isNaN(value)) {
1766
+ return {
1767
+ id: `issue_${mutation.id}_${diff.path}_nan`,
1768
+ category: "invalid-value",
1769
+ severity: "critical",
1770
+ title: `NaN value at: ${diff.path}`,
1771
+ description: `The value at "${diff.path}" became NaN. This usually indicates a calculation with undefined/null.`,
1772
+ mutationId: mutation.id,
1773
+ mutationIndex: index,
1774
+ path: diff.path,
1775
+ previousValue: diff.oldValue,
1776
+ currentValue: value,
1777
+ suggestion: "Check for undefined/null values before calculation. Use default values: (value ?? 0)",
1778
+ timestamp: mutation.timestamp
1779
+ };
1780
+ }
1781
+ if (value === void 0 && diff.oldValue !== void 0 && diff.operation === "UPDATE") {
1782
+ return {
1783
+ id: `issue_${mutation.id}_${diff.path}_undefined`,
1784
+ category: "invalid-value",
1785
+ severity: "critical",
1786
+ title: `Value became undefined: ${diff.path}`,
1787
+ description: `The field "${diff.path}" changed from a defined value to undefined.`,
1788
+ mutationId: mutation.id,
1789
+ mutationIndex: index,
1790
+ path: diff.path,
1791
+ previousValue: diff.oldValue,
1792
+ currentValue: void 0,
1793
+ suggestion: "Ensure the value is always defined or explicitly handle undefined cases.",
1794
+ timestamp: mutation.timestamp
1795
+ };
1796
+ }
1797
+ return null;
1798
+ }
1799
+ /**
1800
+ * Check for unexpected type changes
1801
+ */
1802
+ checkTypeChange(diff, mutation, index) {
1803
+ if (diff.oldValue === void 0 || diff.oldValue === null) return null;
1804
+ if (diff.newValue === void 0 || diff.newValue === null) return null;
1805
+ const oldType = typeof diff.oldValue;
1806
+ const newType = typeof diff.newValue;
1807
+ if (oldType !== newType) {
1808
+ return {
1809
+ id: `issue_${mutation.id}_${diff.path}_type`,
1810
+ category: "type-change",
1811
+ severity: "warning",
1812
+ title: `Type changed: ${diff.path}`,
1813
+ description: `The type of "${diff.path}" changed from ${oldType} to ${newType}.`,
1814
+ mutationId: mutation.id,
1815
+ mutationIndex: index,
1816
+ path: diff.path,
1817
+ previousValue: diff.oldValue,
1818
+ currentValue: diff.newValue,
1819
+ suggestion: "Ensure consistent types. Use TypeScript or runtime validation.",
1820
+ timestamp: mutation.timestamp
1821
+ };
1822
+ }
1823
+ return null;
1824
+ }
1825
+ /**
1826
+ * Check if mutation is a no-op (no actual changes)
1827
+ */
1828
+ isNoOpUpdate(mutation) {
1829
+ if (!mutation.diff || mutation.diff.length === 0) {
1830
+ return true;
1831
+ }
1832
+ return mutation.diff.every((d) => {
1833
+ if (d.operation !== "UPDATE") return false;
1834
+ return JSON.stringify(d.oldValue) === JSON.stringify(d.newValue);
1835
+ });
1836
+ }
1837
+ /**
1838
+ * Check for excessive updates in short time period
1839
+ */
1840
+ checkExcessiveUpdates(mutation, index) {
1841
+ const key = mutation.component || mutation.source;
1842
+ const timestamps = this.updateTimestamps.get(key) || [];
1843
+ timestamps.push(mutation.timestamp);
1844
+ const cutoff = mutation.timestamp - 100;
1845
+ const recentTimestamps = timestamps.filter((t) => t >= cutoff);
1846
+ this.updateTimestamps.set(key, recentTimestamps);
1847
+ if (recentTimestamps.length > 5) {
1848
+ return {
1849
+ id: `issue_${mutation.id}_excessive`,
1850
+ category: "excessive-updates",
1851
+ severity: "warning",
1852
+ title: `Excessive updates from: ${key}`,
1853
+ description: `${recentTimestamps.length} mutations in 100ms from "${key}". This may cause performance issues.`,
1854
+ mutationId: mutation.id,
1855
+ mutationIndex: index,
1856
+ suggestion: "Use debouncing, batching, or memoization to reduce update frequency.",
1857
+ timestamp: mutation.timestamp
1858
+ };
1859
+ }
1860
+ return null;
1861
+ }
1862
+ /**
1863
+ * Check custom invariant rule
1864
+ */
1865
+ checkInvariant(invariant, mutation, index) {
1866
+ if (!mutation.nextState) return null;
1867
+ const value = getValueAtPath(mutation.nextState, invariant.path);
1868
+ try {
1869
+ const isValid = invariant.rule(value);
1870
+ if (!isValid) {
1871
+ return {
1872
+ id: `issue_${mutation.id}_invariant_${invariant.name}`,
1873
+ category: "broken-invariant",
1874
+ severity: "critical",
1875
+ title: `Invariant violated: ${invariant.name}`,
1876
+ description: invariant.message,
1877
+ mutationId: mutation.id,
1878
+ mutationIndex: index,
1879
+ path: invariant.path,
1880
+ currentValue: value,
1881
+ suggestion: `Ensure the invariant "${invariant.name}" is always maintained.`,
1882
+ timestamp: mutation.timestamp
1883
+ };
1884
+ }
1885
+ } catch (e) {
1886
+ return null;
1887
+ }
1888
+ return null;
1889
+ }
1890
+ /**
1891
+ * Analyze entire timeline for issues
1892
+ */
1893
+ analyzeTimeline(timeline) {
1894
+ const allIssues = [];
1895
+ this.updateTimestamps.clear();
1896
+ for (let i = 0; i < timeline.length; i++) {
1897
+ const issues = this.analyzeMutation(timeline[i], i, timeline);
1898
+ allIssues.push(...issues);
1899
+ }
1900
+ return allIssues;
1901
+ }
1902
+ /**
1903
+ * Build dependency graph showing which components touch which state paths
1904
+ */
1905
+ buildDependencyGraph(timeline) {
1906
+ const nodes = /* @__PURE__ */ new Map();
1907
+ for (const mutation of timeline) {
1908
+ if (!mutation.diff) continue;
1909
+ const component = mutation.component || mutation.source || "unknown";
1910
+ for (const diff of mutation.diff) {
1911
+ const existing = nodes.get(diff.path);
1912
+ if (existing) {
1913
+ existing.components.add(component);
1914
+ existing.mutationCount++;
1915
+ existing.lastMutationId = mutation.id;
1916
+ } else {
1917
+ nodes.set(diff.path, {
1918
+ path: diff.path,
1919
+ components: /* @__PURE__ */ new Set([component]),
1920
+ mutationCount: 1,
1921
+ lastMutationId: mutation.id
1922
+ });
1923
+ }
1924
+ }
1925
+ }
1926
+ const couplings = [];
1927
+ for (const [path, node] of nodes) {
1928
+ if (node.components.size > 1) {
1929
+ couplings.push({
1930
+ path,
1931
+ components: Array.from(node.components),
1932
+ severity: node.components.size > 2 ? "critical" : "warning"
1933
+ });
1934
+ }
1935
+ }
1936
+ return { nodes, couplings };
1937
+ }
1938
+ /**
1939
+ * Find causal chain - which mutations caused downstream effects
1940
+ */
1941
+ findCausalChain(mutationId, timeline) {
1942
+ const rootIndex = timeline.findIndex((m) => m.id === mutationId);
1943
+ if (rootIndex === -1) return null;
1944
+ const root = timeline[rootIndex];
1945
+ const effects = [];
1946
+ if (!root.diff) return { rootMutation: root, effects };
1947
+ const changedPaths = new Set(root.diff.map((d) => d.path));
1948
+ for (let i = rootIndex + 1; i < timeline.length; i++) {
1949
+ const mutation = timeline[i];
1950
+ if (!mutation.diff) continue;
1951
+ for (const diff of mutation.diff) {
1952
+ for (const changedPath of changedPaths) {
1953
+ if (diff.path.startsWith(changedPath) || changedPath.startsWith(diff.path)) {
1954
+ effects.push({
1955
+ mutation,
1956
+ causedBy: root.id,
1957
+ reason: `Uses path "${diff.path}" which was affected by "${changedPath}"`
1958
+ });
1959
+ break;
1960
+ }
1961
+ }
1962
+ }
1963
+ }
1964
+ return { rootMutation: root, effects };
1965
+ }
1966
+ /**
1967
+ * Find the first mutation that corrupted state
1968
+ * Uses binary search for efficiency
1969
+ */
1970
+ findCorruptionPoint(timeline, validator) {
1971
+ let left = 0;
1972
+ let right = timeline.length - 1;
1973
+ let result = null;
1974
+ while (left <= right) {
1975
+ const mid = Math.floor((left + right) / 2);
1976
+ const mutation = timeline[mid];
1977
+ if (mutation.nextState && !validator(mutation.nextState)) {
1978
+ result = { mutation, index: mid };
1979
+ right = mid - 1;
1980
+ } else {
1981
+ left = mid + 1;
1982
+ }
1983
+ }
1984
+ return result;
1985
+ }
1986
+ };
1987
+ function getValueAtPath(obj, path) {
1988
+ if (!path) return obj;
1989
+ const parts = path.split(".");
1990
+ let current = obj;
1991
+ for (const part of parts) {
1992
+ if (current === null || current === void 0) return void 0;
1993
+ if (typeof current !== "object") return void 0;
1994
+ current = current[part];
1995
+ }
1996
+ return current;
1997
+ }
1998
+ function DependencyGraph({
1999
+ graph,
2000
+ onPathSelect,
2001
+ selectedPath,
2002
+ className = ""
2003
+ }) {
2004
+ const [viewMode, setViewMode] = React4.useState("couplings");
2005
+ const [hoveredPath, setHoveredPath] = React4.useState(null);
2006
+ const sortedNodes = React4.useMemo(() => {
2007
+ const nodes = Array.from(graph.nodes.values());
2008
+ return nodes.sort((a, b) => b.mutationCount - a.mutationCount);
2009
+ }, [graph.nodes]);
2010
+ const allComponents = React4.useMemo(() => {
2011
+ const components = /* @__PURE__ */ new Set();
2012
+ for (const node of graph.nodes.values()) {
2013
+ for (const c of node.components) {
2014
+ components.add(c);
2015
+ }
2016
+ }
2017
+ return Array.from(components);
2018
+ }, [graph.nodes]);
2019
+ const componentColors = React4.useMemo(() => {
2020
+ const colors = {};
2021
+ const palette = [
2022
+ "#61dafb",
2023
+ "#764abc",
2024
+ "#f59e0b",
2025
+ "#22c55e",
2026
+ "#ef4444",
2027
+ "#a78bfa",
2028
+ "#f472b6",
2029
+ "#06b6d4",
2030
+ "#84cc16",
2031
+ "#f97316"
2032
+ ];
2033
+ allComponents.forEach((comp, i) => {
2034
+ colors[comp] = palette[i % palette.length];
2035
+ });
2036
+ return colors;
2037
+ }, [allComponents]);
2038
+ const handlePathClick = React4.useCallback((path) => {
2039
+ onPathSelect?.(path);
2040
+ }, [onPathSelect]);
2041
+ const displayNodes = viewMode === "couplings" ? sortedNodes.filter((n) => n.components.size > 1) : sortedNodes;
2042
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `dependency-graph ${className}`, children: [
2043
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "dg-header", children: [
2044
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dg-title", children: "\u{1F517} State Dependencies" }),
2045
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "dg-controls", children: [
2046
+ /* @__PURE__ */ jsxRuntime.jsxs(
2047
+ "button",
2048
+ {
2049
+ className: `dg-view-btn ${viewMode === "couplings" ? "active" : ""}`,
2050
+ onClick: () => setViewMode("couplings"),
2051
+ children: [
2052
+ "\u26A0\uFE0F Couplings Only (",
2053
+ graph.couplings.length,
2054
+ ")"
2055
+ ]
2056
+ }
2057
+ ),
2058
+ /* @__PURE__ */ jsxRuntime.jsxs(
2059
+ "button",
2060
+ {
2061
+ className: `dg-view-btn ${viewMode === "all" ? "active" : ""}`,
2062
+ onClick: () => setViewMode("all"),
2063
+ children: [
2064
+ "\u{1F4CA} All Paths (",
2065
+ graph.nodes.size,
2066
+ ")"
2067
+ ]
2068
+ }
2069
+ )
2070
+ ] })
2071
+ ] }),
2072
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "dg-legend", children: allComponents.map((comp) => /* @__PURE__ */ jsxRuntime.jsxs(
2073
+ "span",
2074
+ {
2075
+ className: "dg-legend-item",
2076
+ style: { "--comp-color": componentColors[comp] },
2077
+ children: [
2078
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dg-legend-dot" }),
2079
+ comp
2080
+ ]
2081
+ },
2082
+ comp
2083
+ )) }),
2084
+ graph.couplings.length > 0 && viewMode === "couplings" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "dg-alert", children: [
2085
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dg-alert-icon", children: "\u26A0\uFE0F" }),
2086
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "dg-alert-content", children: [
2087
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Hidden State Coupling Detected" }),
2088
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
2089
+ graph.couplings.length,
2090
+ " state path",
2091
+ graph.couplings.length > 1 ? "s are" : " is",
2092
+ " accessed by multiple components. This can cause unexpected state conflicts."
2093
+ ] })
2094
+ ] })
2095
+ ] }),
2096
+ displayNodes.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "dg-empty", children: [
2097
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dg-empty-icon", children: "\u2705" }),
2098
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "No state coupling detected" })
2099
+ ] }),
2100
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "dg-paths", children: displayNodes.map((node) => {
2101
+ const isCoupled = node.components.size > 1;
2102
+ const coupling = graph.couplings.find((c) => c.path === node.path);
2103
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2104
+ "div",
2105
+ {
2106
+ className: `dg-path ${isCoupled ? "dg-path--coupled" : ""} ${selectedPath === node.path ? "selected" : ""} ${hoveredPath === node.path ? "hovered" : ""}`,
2107
+ onClick: () => handlePathClick(node.path),
2108
+ onMouseEnter: () => setHoveredPath(node.path),
2109
+ onMouseLeave: () => setHoveredPath(null),
2110
+ children: [
2111
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "dg-path-header", children: [
2112
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: "dg-path-name", children: node.path }),
2113
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "dg-path-count", children: [
2114
+ node.mutationCount,
2115
+ " mutation",
2116
+ node.mutationCount > 1 ? "s" : ""
2117
+ ] })
2118
+ ] }),
2119
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "dg-path-components", children: Array.from(node.components).map((comp) => /* @__PURE__ */ jsxRuntime.jsx(
2120
+ "span",
2121
+ {
2122
+ className: "dg-component-tag",
2123
+ style: {
2124
+ backgroundColor: componentColors[comp] + "30",
2125
+ borderColor: componentColors[comp],
2126
+ color: componentColors[comp]
2127
+ },
2128
+ children: comp
2129
+ },
2130
+ comp
2131
+ )) }),
2132
+ isCoupled && coupling && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `dg-coupling-warning dg-coupling-${coupling.severity}`, children: [
2133
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dg-warning-icon", children: coupling.severity === "critical" ? "\u{1F534}" : "\u{1F7E1}" }),
2134
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "dg-warning-text", children: [
2135
+ coupling.components.length,
2136
+ " components access this path"
2137
+ ] })
2138
+ ] })
2139
+ ]
2140
+ },
2141
+ node.path
2142
+ );
2143
+ }) }),
2144
+ displayNodes.length > 0 && displayNodes.length <= 20 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "dg-visual", children: [
2145
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "dg-visual-title", children: "Dependency Visualization" }),
2146
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { className: "dg-svg", viewBox: "0 0 400 300", children: [
2147
+ allComponents.map((comp, i) => {
2148
+ const x = 50 + i * (350 / Math.max(allComponents.length, 1));
2149
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
2150
+ /* @__PURE__ */ jsxRuntime.jsx(
2151
+ "circle",
2152
+ {
2153
+ cx: x,
2154
+ cy: 40,
2155
+ r: 20,
2156
+ fill: componentColors[comp],
2157
+ opacity: 0.8
2158
+ }
2159
+ ),
2160
+ /* @__PURE__ */ jsxRuntime.jsx(
2161
+ "text",
2162
+ {
2163
+ x,
2164
+ y: 75,
2165
+ textAnchor: "middle",
2166
+ fill: "#888",
2167
+ fontSize: "10",
2168
+ children: comp.length > 10 ? comp.slice(0, 10) + "..." : comp
2169
+ }
2170
+ )
2171
+ ] }, comp);
2172
+ }),
2173
+ displayNodes.slice(0, 8).map((node, i) => {
2174
+ const x = 50 + i * (350 / Math.max(displayNodes.length, 1));
2175
+ const isCoupled = node.components.size > 1;
2176
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
2177
+ Array.from(node.components).map((comp) => {
2178
+ const compIndex = allComponents.indexOf(comp);
2179
+ const compX = 50 + compIndex * (350 / Math.max(allComponents.length, 1));
2180
+ return /* @__PURE__ */ jsxRuntime.jsx(
2181
+ "line",
2182
+ {
2183
+ x1: compX,
2184
+ y1: 60,
2185
+ x2: x,
2186
+ y2: 180,
2187
+ stroke: componentColors[comp],
2188
+ strokeWidth: isCoupled ? 2 : 1,
2189
+ opacity: isCoupled ? 0.8 : 0.3
2190
+ },
2191
+ comp
2192
+ );
2193
+ }),
2194
+ /* @__PURE__ */ jsxRuntime.jsx(
2195
+ "rect",
2196
+ {
2197
+ x: x - 25,
2198
+ y: 180,
2199
+ width: 50,
2200
+ height: 24,
2201
+ rx: 4,
2202
+ fill: isCoupled ? "#ef444430" : "#16213e",
2203
+ stroke: isCoupled ? "#ef4444" : "#333"
2204
+ }
2205
+ ),
2206
+ /* @__PURE__ */ jsxRuntime.jsx(
2207
+ "text",
2208
+ {
2209
+ x,
2210
+ y: 196,
2211
+ textAnchor: "middle",
2212
+ fill: isCoupled ? "#fca5a5" : "#888",
2213
+ fontSize: "8",
2214
+ children: node.path.length > 8 ? node.path.slice(-8) : node.path
2215
+ }
2216
+ )
2217
+ ] }, node.path);
2218
+ }),
2219
+ displayNodes.length > 8 && /* @__PURE__ */ jsxRuntime.jsxs("text", { x: 200, y: 250, textAnchor: "middle", fill: "#666", fontSize: "12", children: [
2220
+ "+",
2221
+ displayNodes.length - 8,
2222
+ " more paths..."
2223
+ ] })
2224
+ ] })
2225
+ ] }),
2226
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: styles })
2227
+ ] });
2228
+ }
2229
+ var styles = `
2230
+ .dependency-graph {
2231
+ background: #1a1a2e;
2232
+ color: #e0e0e0;
2233
+ font-family: system-ui, sans-serif;
2234
+ height: 100%;
2235
+ display: flex;
2236
+ flex-direction: column;
2237
+ }
2238
+
2239
+ .dg-header {
2240
+ display: flex;
2241
+ justify-content: space-between;
2242
+ align-items: center;
2243
+ padding: 0.75rem 1rem;
2244
+ background: #16213e;
2245
+ border-bottom: 1px solid #333;
2246
+ flex-wrap: wrap;
2247
+ gap: 0.5rem;
2248
+ }
2249
+
2250
+ .dg-title {
2251
+ font-weight: 600;
2252
+ color: #00d9ff;
2253
+ }
2254
+
2255
+ .dg-controls {
2256
+ display: flex;
2257
+ gap: 0.5rem;
2258
+ }
2259
+
2260
+ .dg-view-btn {
2261
+ padding: 0.375rem 0.75rem;
2262
+ border: 1px solid #333;
2263
+ border-radius: 6px;
2264
+ background: transparent;
2265
+ color: #888;
2266
+ font-size: 0.8rem;
2267
+ cursor: pointer;
2268
+ transition: all 0.2s;
2269
+ }
2270
+
2271
+ .dg-view-btn:hover {
2272
+ background: #16213e;
2273
+ color: #ccc;
2274
+ }
2275
+
2276
+ .dg-view-btn.active {
2277
+ background: #00d9ff20;
2278
+ border-color: #00d9ff;
2279
+ color: #00d9ff;
2280
+ }
2281
+
2282
+ .dg-legend {
2283
+ display: flex;
2284
+ flex-wrap: wrap;
2285
+ gap: 0.75rem;
2286
+ padding: 0.75rem 1rem;
2287
+ background: #0f0f23;
2288
+ border-bottom: 1px solid #333;
2289
+ }
2290
+
2291
+ .dg-legend-item {
2292
+ display: flex;
2293
+ align-items: center;
2294
+ gap: 0.375rem;
2295
+ font-size: 0.8rem;
2296
+ color: #888;
2297
+ }
2298
+
2299
+ .dg-legend-dot {
2300
+ width: 10px;
2301
+ height: 10px;
2302
+ border-radius: 50%;
2303
+ background: var(--comp-color);
2304
+ }
2305
+
2306
+ .dg-alert {
2307
+ display: flex;
2308
+ gap: 0.75rem;
2309
+ padding: 1rem;
2310
+ margin: 0.75rem;
2311
+ background: #ef444420;
2312
+ border: 1px solid #ef4444;
2313
+ border-radius: 8px;
2314
+ }
2315
+
2316
+ .dg-alert-icon {
2317
+ font-size: 1.25rem;
2318
+ }
2319
+
2320
+ .dg-alert-content strong {
2321
+ color: #fca5a5;
2322
+ display: block;
2323
+ margin-bottom: 0.25rem;
2324
+ }
2325
+
2326
+ .dg-alert-content p {
2327
+ margin: 0;
2328
+ font-size: 0.875rem;
2329
+ color: #ccc;
2330
+ }
2331
+
2332
+ .dg-empty {
2333
+ flex: 1;
2334
+ display: flex;
2335
+ flex-direction: column;
2336
+ align-items: center;
2337
+ justify-content: center;
2338
+ color: #888;
2339
+ }
2340
+
2341
+ .dg-empty-icon {
2342
+ font-size: 2.5rem;
2343
+ margin-bottom: 0.5rem;
2344
+ }
2345
+
2346
+ .dg-paths {
2347
+ flex: 1;
2348
+ overflow: auto;
2349
+ padding: 0.75rem;
2350
+ }
2351
+
2352
+ .dg-path {
2353
+ background: #16213e;
2354
+ border-radius: 8px;
2355
+ padding: 0.875rem 1rem;
2356
+ margin-bottom: 0.625rem;
2357
+ cursor: pointer;
2358
+ border: 1px solid transparent;
2359
+ transition: all 0.2s;
2360
+ }
2361
+
2362
+ .dg-path:hover, .dg-path.hovered {
2363
+ border-color: #333;
2364
+ background: #1a2744;
2365
+ }
2366
+
2367
+ .dg-path.selected {
2368
+ border-color: #00d9ff;
2369
+ box-shadow: 0 0 0 2px #00d9ff30;
2370
+ }
2371
+
2372
+ .dg-path--coupled {
2373
+ border-left: 3px solid #ef4444;
2374
+ }
2375
+
2376
+ .dg-path-header {
2377
+ display: flex;
2378
+ justify-content: space-between;
2379
+ align-items: center;
2380
+ margin-bottom: 0.5rem;
2381
+ }
2382
+
2383
+ .dg-path-name {
2384
+ font-family: 'Fira Code', monospace;
2385
+ color: #a78bfa;
2386
+ font-size: 0.875rem;
2387
+ }
2388
+
2389
+ .dg-path-count {
2390
+ font-size: 0.75rem;
2391
+ color: #666;
2392
+ }
2393
+
2394
+ .dg-path-components {
2395
+ display: flex;
2396
+ flex-wrap: wrap;
2397
+ gap: 0.375rem;
2398
+ }
2399
+
2400
+ .dg-component-tag {
2401
+ padding: 0.25rem 0.5rem;
2402
+ border-radius: 4px;
2403
+ font-size: 0.75rem;
2404
+ border: 1px solid;
2405
+ }
2406
+
2407
+ .dg-coupling-warning {
2408
+ display: flex;
2409
+ align-items: center;
2410
+ gap: 0.375rem;
2411
+ margin-top: 0.625rem;
2412
+ padding: 0.375rem 0.5rem;
2413
+ border-radius: 4px;
2414
+ font-size: 0.75rem;
2415
+ }
2416
+
2417
+ .dg-coupling-critical {
2418
+ background: #ef444420;
2419
+ color: #fca5a5;
2420
+ }
2421
+
2422
+ .dg-coupling-warning {
2423
+ background: #fbbf2420;
2424
+ color: #fcd34d;
2425
+ }
2426
+
2427
+ .dg-visual {
2428
+ padding: 1rem;
2429
+ border-top: 1px solid #333;
2430
+ }
2431
+
2432
+ .dg-visual-title {
2433
+ font-size: 0.8rem;
2434
+ color: #888;
2435
+ margin-bottom: 0.5rem;
2436
+ }
2437
+
2438
+ .dg-svg {
2439
+ width: 100%;
2440
+ max-height: 200px;
2441
+ background: #0f0f23;
2442
+ border-radius: 8px;
2443
+ }
2444
+ `;
2445
+ var severityIcons = {
2446
+ critical: "\u{1F534}",
2447
+ warning: "\u{1F7E1}",
2448
+ info: "\u{1F7E2}"
2449
+ };
2450
+ var categoryIcons = {
2451
+ "state-loss": "\u{1F4E4}",
2452
+ "invalid-value": "\u26A0\uFE0F",
2453
+ "type-change": "\u{1F504}",
2454
+ "no-op-update": "\u267B\uFE0F",
2455
+ "excessive-updates": "\u26A1",
2456
+ "broken-invariant": "\u{1F6AB}",
2457
+ "hidden-coupling": "\u{1F517}",
2458
+ "temporal-anomaly": "\u23F0",
2459
+ "state-corruption": "\u{1F4A5}"
2460
+ };
2461
+ var categoryLabels = {
2462
+ "state-loss": "State Loss",
2463
+ "invalid-value": "Invalid Value",
2464
+ "type-change": "Type Change",
2465
+ "no-op-update": "Redundant Update",
2466
+ "excessive-updates": "Excessive Updates",
2467
+ "broken-invariant": "Broken Invariant",
2468
+ "hidden-coupling": "Hidden Coupling",
2469
+ "temporal-anomaly": "Temporal Anomaly",
2470
+ "state-corruption": "State Corruption"
2471
+ };
2472
+ function IssuesPanel({
2473
+ issues,
2474
+ onJumpToMutation,
2475
+ onSelectIssue,
2476
+ selectedIssueId,
2477
+ className = ""
2478
+ }) {
2479
+ const [severityFilter, setSeverityFilter] = React4.useState("all");
2480
+ const [categoryFilter, setCategoryFilter] = React4.useState("all");
2481
+ const [expandedIssue, setExpandedIssue] = React4.useState(null);
2482
+ const filteredIssues = React4.useMemo(() => {
2483
+ return issues.filter((issue) => {
2484
+ if (severityFilter !== "all" && issue.severity !== severityFilter) return false;
2485
+ if (categoryFilter !== "all" && issue.category !== categoryFilter) return false;
2486
+ return true;
2487
+ });
2488
+ }, [issues, severityFilter, categoryFilter]);
2489
+ const issueSummary = React4.useMemo(() => {
2490
+ const summary = { critical: 0, warning: 0, info: 0 };
2491
+ for (const issue of issues) {
2492
+ summary[issue.severity]++;
2493
+ }
2494
+ return summary;
2495
+ }, [issues]);
2496
+ const availableCategories = React4.useMemo(() => {
2497
+ return Array.from(new Set(issues.map((i) => i.category)));
2498
+ }, [issues]);
2499
+ const handleIssueClick = (issue) => {
2500
+ if (expandedIssue === issue.id) {
2501
+ setExpandedIssue(null);
2502
+ } else {
2503
+ setExpandedIssue(issue.id);
2504
+ onSelectIssue?.(issue);
2505
+ }
2506
+ };
2507
+ if (issues.length === 0) {
2508
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `issues-panel issues-panel--empty ${className}`, children: [
2509
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-empty", children: [
2510
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ip-empty-icon", children: "\u2705" }),
2511
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "No Issues Detected" }),
2512
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Your state timeline looks clean!" })
2513
+ ] }),
2514
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: styles2 })
2515
+ ] });
2516
+ }
2517
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `issues-panel ${className}`, children: [
2518
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-summary", children: [
2519
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-summary-title", children: [
2520
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ip-summary-icon", children: "\u{1F6A8}" }),
2521
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
2522
+ issues.length,
2523
+ " Issue",
2524
+ issues.length !== 1 ? "s" : "",
2525
+ " Detected"
2526
+ ] })
2527
+ ] }),
2528
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-summary-counts", children: [
2529
+ issueSummary.critical > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ip-count ip-count--critical", children: [
2530
+ severityIcons.critical,
2531
+ " ",
2532
+ issueSummary.critical
2533
+ ] }),
2534
+ issueSummary.warning > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ip-count ip-count--warning", children: [
2535
+ severityIcons.warning,
2536
+ " ",
2537
+ issueSummary.warning
2538
+ ] }),
2539
+ issueSummary.info > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ip-count ip-count--info", children: [
2540
+ severityIcons.info,
2541
+ " ",
2542
+ issueSummary.info
2543
+ ] })
2544
+ ] })
2545
+ ] }),
2546
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-filters", children: [
2547
+ /* @__PURE__ */ jsxRuntime.jsxs(
2548
+ "select",
2549
+ {
2550
+ value: severityFilter,
2551
+ onChange: (e) => setSeverityFilter(e.target.value),
2552
+ className: "ip-filter",
2553
+ children: [
2554
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: "All Severities" }),
2555
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "critical", children: "\u{1F534} Critical" }),
2556
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "warning", children: "\u{1F7E1} Warning" }),
2557
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "info", children: "\u{1F7E2} Info" })
2558
+ ]
2559
+ }
2560
+ ),
2561
+ /* @__PURE__ */ jsxRuntime.jsxs(
2562
+ "select",
2563
+ {
2564
+ value: categoryFilter,
2565
+ onChange: (e) => setCategoryFilter(e.target.value),
2566
+ className: "ip-filter",
2567
+ children: [
2568
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: "All Categories" }),
2569
+ availableCategories.map((cat) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: cat, children: [
2570
+ categoryIcons[cat],
2571
+ " ",
2572
+ categoryLabels[cat]
2573
+ ] }, cat))
2574
+ ]
2575
+ }
2576
+ )
2577
+ ] }),
2578
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ip-list", children: filteredIssues.map((issue) => /* @__PURE__ */ jsxRuntime.jsxs(
2579
+ "div",
2580
+ {
2581
+ className: `ip-issue ip-issue--${issue.severity} ${selectedIssueId === issue.id ? "selected" : ""} ${expandedIssue === issue.id ? "expanded" : ""}`,
2582
+ onClick: () => handleIssueClick(issue),
2583
+ children: [
2584
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-issue-header", children: [
2585
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ip-issue-severity", children: severityIcons[issue.severity] }),
2586
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ip-issue-category", children: categoryIcons[issue.category] }),
2587
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ip-issue-title", children: issue.title }),
2588
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ip-issue-index", children: [
2589
+ "#",
2590
+ issue.mutationIndex + 1
2591
+ ] })
2592
+ ] }),
2593
+ expandedIssue === issue.id && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-issue-details", children: [
2594
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "ip-issue-description", children: issue.description }),
2595
+ issue.path && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-issue-field", children: [
2596
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ip-field-label", children: "Path:" }),
2597
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: "ip-field-value", children: issue.path })
2598
+ ] }),
2599
+ issue.previousValue !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-issue-field", children: [
2600
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ip-field-label", children: "Before:" }),
2601
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: "ip-field-value ip-value-old", children: formatValue(issue.previousValue) })
2602
+ ] }),
2603
+ issue.currentValue !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-issue-field", children: [
2604
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ip-field-label", children: "After:" }),
2605
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: `ip-field-value ip-value-new ${issue.severity === "critical" ? "ip-value-bad" : ""}`, children: formatValue(issue.currentValue) })
2606
+ ] }),
2607
+ issue.suggestion && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-issue-suggestion", children: [
2608
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ip-suggestion-icon", children: "\u{1F4A1}" }),
2609
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: issue.suggestion })
2610
+ ] }),
2611
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ip-issue-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
2612
+ "button",
2613
+ {
2614
+ className: "ip-action-btn",
2615
+ onClick: (e) => {
2616
+ e.stopPropagation();
2617
+ onJumpToMutation(issue.mutationId, issue.mutationIndex);
2618
+ },
2619
+ children: "\u{1F3AF} Jump to Mutation"
2620
+ }
2621
+ ) })
2622
+ ] })
2623
+ ]
2624
+ },
2625
+ issue.id
2626
+ )) }),
2627
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: styles2 })
2628
+ ] });
2629
+ }
2630
+ function formatValue(value) {
2631
+ if (value === void 0) return "undefined";
2632
+ if (value === null) return "null";
2633
+ if (typeof value === "number" && isNaN(value)) return "NaN";
2634
+ if (typeof value === "object") {
2635
+ const str = JSON.stringify(value);
2636
+ return str.length > 50 ? str.slice(0, 50) + "..." : str;
2637
+ }
2638
+ return String(value);
2639
+ }
2640
+ var styles2 = `
2641
+ .issues-panel {
2642
+ background: #1a1a2e;
2643
+ color: #e0e0e0;
2644
+ font-family: system-ui, sans-serif;
2645
+ height: 100%;
2646
+ display: flex;
2647
+ flex-direction: column;
2648
+ }
2649
+
2650
+ .issues-panel--empty {
2651
+ align-items: center;
2652
+ justify-content: center;
2653
+ }
2654
+
2655
+ .ip-empty {
2656
+ text-align: center;
2657
+ padding: 3rem;
2658
+ }
2659
+
2660
+ .ip-empty-icon {
2661
+ font-size: 4rem;
2662
+ display: block;
2663
+ margin-bottom: 1rem;
2664
+ }
2665
+
2666
+ .ip-empty h3 {
2667
+ margin: 0 0 0.5rem;
2668
+ color: #22c55e;
2669
+ }
2670
+
2671
+ .ip-empty p {
2672
+ margin: 0;
2673
+ color: #888;
2674
+ }
2675
+
2676
+ .ip-summary {
2677
+ display: flex;
2678
+ justify-content: space-between;
2679
+ align-items: center;
2680
+ padding: 1rem 1.25rem;
2681
+ background: linear-gradient(135deg, #ef444420, #fbbf2410);
2682
+ border-bottom: 1px solid #333;
2683
+ }
2684
+
2685
+ .ip-summary-title {
2686
+ display: flex;
2687
+ align-items: center;
2688
+ gap: 0.5rem;
2689
+ font-weight: 600;
2690
+ color: #fca5a5;
2691
+ }
2692
+
2693
+ .ip-summary-icon {
2694
+ font-size: 1.25rem;
2695
+ }
2696
+
2697
+ .ip-summary-counts {
2698
+ display: flex;
2699
+ gap: 0.75rem;
2700
+ }
2701
+
2702
+ .ip-count {
2703
+ padding: 0.25rem 0.75rem;
2704
+ border-radius: 12px;
2705
+ font-size: 0.875rem;
2706
+ font-weight: 600;
2707
+ }
2708
+
2709
+ .ip-count--critical { background: #ef444430; color: #fca5a5; }
2710
+ .ip-count--warning { background: #fbbf2430; color: #fcd34d; }
2711
+ .ip-count--info { background: #22c55e30; color: #86efac; }
2712
+
2713
+ .ip-filters {
2714
+ display: flex;
2715
+ gap: 0.75rem;
2716
+ padding: 0.75rem 1.25rem;
2717
+ background: #16213e;
2718
+ border-bottom: 1px solid #333;
2719
+ }
2720
+
2721
+ .ip-filter {
2722
+ flex: 1;
2723
+ padding: 0.5rem 0.75rem;
2724
+ border: 1px solid #333;
2725
+ border-radius: 6px;
2726
+ background: #0f0f23;
2727
+ color: #e0e0e0;
2728
+ font-size: 0.875rem;
2729
+ cursor: pointer;
2730
+ }
2731
+
2732
+ .ip-list {
2733
+ flex: 1;
2734
+ overflow: auto;
2735
+ padding: 0.75rem;
2736
+ }
2737
+
2738
+ .ip-issue {
2739
+ background: #16213e;
2740
+ border-radius: 8px;
2741
+ margin-bottom: 0.75rem;
2742
+ border-left: 4px solid;
2743
+ cursor: pointer;
2744
+ transition: all 0.2s;
2745
+ }
2746
+
2747
+ .ip-issue:hover {
2748
+ background: #1a2744;
2749
+ }
2750
+
2751
+ .ip-issue--critical { border-left-color: #ef4444; }
2752
+ .ip-issue--warning { border-left-color: #fbbf24; }
2753
+ .ip-issue--info { border-left-color: #22c55e; }
2754
+
2755
+ .ip-issue.selected {
2756
+ box-shadow: 0 0 0 2px #00d9ff40;
2757
+ }
2758
+
2759
+ .ip-issue-header {
2760
+ display: flex;
2761
+ align-items: center;
2762
+ gap: 0.625rem;
2763
+ padding: 0.875rem 1rem;
2764
+ }
2765
+
2766
+ .ip-issue-severity {
2767
+ font-size: 0.875rem;
2768
+ }
2769
+
2770
+ .ip-issue-category {
2771
+ font-size: 1rem;
2772
+ }
2773
+
2774
+ .ip-issue-title {
2775
+ flex: 1;
2776
+ font-size: 0.875rem;
2777
+ font-weight: 500;
2778
+ }
2779
+
2780
+ .ip-issue-index {
2781
+ font-size: 0.75rem;
2782
+ color: #666;
2783
+ font-family: 'Fira Code', monospace;
2784
+ }
2785
+
2786
+ .ip-issue-details {
2787
+ padding: 0 1rem 1rem;
2788
+ border-top: 1px solid #333;
2789
+ margin-top: 0.5rem;
2790
+ padding-top: 0.75rem;
2791
+ }
2792
+
2793
+ .ip-issue-description {
2794
+ margin: 0 0 1rem;
2795
+ font-size: 0.875rem;
2796
+ color: #ccc;
2797
+ line-height: 1.5;
2798
+ }
2799
+
2800
+ .ip-issue-field {
2801
+ display: flex;
2802
+ align-items: flex-start;
2803
+ gap: 0.75rem;
2804
+ margin-bottom: 0.5rem;
2805
+ font-size: 0.875rem;
2806
+ }
2807
+
2808
+ .ip-field-label {
2809
+ color: #888;
2810
+ min-width: 50px;
2811
+ }
2812
+
2813
+ .ip-field-value {
2814
+ font-family: 'Fira Code', monospace;
2815
+ padding: 0.25rem 0.5rem;
2816
+ background: #0f0f23;
2817
+ border-radius: 4px;
2818
+ font-size: 0.8rem;
2819
+ }
2820
+
2821
+ .ip-value-old {
2822
+ color: #fca5a5;
2823
+ background: #ef444420;
2824
+ }
2825
+
2826
+ .ip-value-new {
2827
+ color: #86efac;
2828
+ background: #22c55e20;
2829
+ }
2830
+
2831
+ .ip-value-bad {
2832
+ color: #fca5a5 !important;
2833
+ background: #ef444420 !important;
2834
+ }
2835
+
2836
+ .ip-issue-suggestion {
2837
+ display: flex;
2838
+ align-items: flex-start;
2839
+ gap: 0.5rem;
2840
+ margin-top: 1rem;
2841
+ padding: 0.75rem;
2842
+ background: #0f0f23;
2843
+ border-radius: 6px;
2844
+ font-size: 0.8rem;
2845
+ color: #a78bfa;
2846
+ }
2847
+
2848
+ .ip-suggestion-icon {
2849
+ flex-shrink: 0;
2850
+ }
2851
+
2852
+ .ip-issue-actions {
2853
+ margin-top: 1rem;
2854
+ display: flex;
2855
+ gap: 0.5rem;
2856
+ }
2857
+
2858
+ .ip-action-btn {
2859
+ padding: 0.5rem 1rem;
2860
+ border: 1px solid #333;
2861
+ border-radius: 6px;
2862
+ background: #0f0f23;
2863
+ color: #e0e0e0;
2864
+ font-size: 0.8rem;
2865
+ cursor: pointer;
2866
+ transition: all 0.2s;
2867
+ }
2868
+
2869
+ .ip-action-btn:hover {
2870
+ background: #16213e;
2871
+ border-color: #00d9ff;
2872
+ color: #00d9ff;
2873
+ }
2874
+ `;
2875
+ function getValueType(value) {
2876
+ if (value === null) return "null";
2877
+ if (value === void 0) return "undefined";
2878
+ if (typeof value === "number" && isNaN(value)) return "nan";
2879
+ if (Array.isArray(value)) return "array";
2880
+ if (typeof value === "function") return "function";
2881
+ return typeof value;
2882
+ }
2883
+ var typeIcons = {
2884
+ null: "\u2298",
2885
+ undefined: "\u2753",
2886
+ boolean: "\u25C9",
2887
+ number: "#",
2888
+ string: '"',
2889
+ array: "[]",
2890
+ object: "{}",
2891
+ nan: "\u26A0\uFE0F",
2892
+ function: "\u0192"
2893
+ };
2894
+ var typeColors = {
2895
+ null: "#f472b6",
2896
+ undefined: "#ef4444",
2897
+ boolean: "#fb923c",
2898
+ number: "#22c55e",
2899
+ string: "#fbbf24",
2900
+ array: "#a78bfa",
2901
+ object: "#60a5fa",
2902
+ nan: "#ef4444",
2903
+ function: "#888"
2904
+ };
2905
+ function StateTreeViewer({
2906
+ state,
2907
+ issuePaths = /* @__PURE__ */ new Set(),
2908
+ changedPaths = /* @__PURE__ */ new Set(),
2909
+ removedPaths = /* @__PURE__ */ new Set(),
2910
+ defaultExpandDepth = 3,
2911
+ searchQuery = "",
2912
+ onPathClick,
2913
+ className = ""
2914
+ }) {
2915
+ const [expandedPaths, setExpandedPaths] = React4.useState(/* @__PURE__ */ new Set());
2916
+ const [copiedPath, setCopiedPath] = React4.useState(null);
2917
+ const toggleExpand = React4.useCallback((path) => {
2918
+ setExpandedPaths((prev) => {
2919
+ const next = new Set(prev);
2920
+ if (next.has(path)) {
2921
+ next.delete(path);
2922
+ } else {
2923
+ next.add(path);
2924
+ }
2925
+ return next;
2926
+ });
2927
+ }, []);
2928
+ const copyPath = React4.useCallback((path, e) => {
2929
+ e.stopPropagation();
2930
+ navigator.clipboard.writeText(path);
2931
+ setCopiedPath(path);
2932
+ setTimeout(() => setCopiedPath(null), 2e3);
2933
+ }, []);
2934
+ const copyValue = React4.useCallback((value, e) => {
2935
+ e.stopPropagation();
2936
+ const text = typeof value === "object" ? JSON.stringify(value, null, 2) : String(value);
2937
+ navigator.clipboard.writeText(text);
2938
+ }, []);
2939
+ const matchingPaths = React4.useMemo(() => {
2940
+ if (!searchQuery) return /* @__PURE__ */ new Set();
2941
+ const matches = /* @__PURE__ */ new Set();
2942
+ function traverse(obj, path) {
2943
+ if (!obj || typeof obj !== "object") return;
2944
+ for (const key of Object.keys(obj)) {
2945
+ const fullPath = path ? `${path}.${key}` : key;
2946
+ if (fullPath.toLowerCase().includes(searchQuery.toLowerCase())) {
2947
+ matches.add(fullPath);
2948
+ const parts = fullPath.split(".");
2949
+ for (let i = 1; i < parts.length; i++) {
2950
+ matches.add(parts.slice(0, i).join("."));
2951
+ }
2952
+ }
2953
+ traverse(obj[key], fullPath);
2954
+ }
2955
+ }
2956
+ traverse(state, "");
2957
+ return matches;
2958
+ }, [state, searchQuery]);
2959
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `state-tree-viewer ${className}`, children: [
2960
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "stv-header", children: [
2961
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-title", children: "\u{1F333} State Tree" }),
2962
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "stv-legend", children: [
2963
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-legend-item stv-legend-changed", children: "\u25CF Changed" }),
2964
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-legend-item stv-legend-removed", children: "\u25CF Removed" }),
2965
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-legend-item stv-legend-issue", children: "\u25CF Issue" })
2966
+ ] })
2967
+ ] }),
2968
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "stv-tree", children: /* @__PURE__ */ jsxRuntime.jsx(
2969
+ TreeNode,
2970
+ {
2971
+ value: state,
2972
+ path: "",
2973
+ depth: 0,
2974
+ issuePaths,
2975
+ changedPaths,
2976
+ removedPaths,
2977
+ expandedPaths,
2978
+ matchingPaths,
2979
+ defaultExpandDepth,
2980
+ searchQuery,
2981
+ copiedPath,
2982
+ onToggle: toggleExpand,
2983
+ onCopyPath: copyPath,
2984
+ onCopyValue: copyValue,
2985
+ onPathClick
2986
+ }
2987
+ ) }),
2988
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: styles3 })
2989
+ ] });
2990
+ }
2991
+ function TreeNode({
2992
+ value,
2993
+ path,
2994
+ depth,
2995
+ keyName,
2996
+ issuePaths,
2997
+ changedPaths,
2998
+ removedPaths,
2999
+ expandedPaths,
3000
+ matchingPaths,
3001
+ defaultExpandDepth,
3002
+ searchQuery,
3003
+ copiedPath,
3004
+ onToggle,
3005
+ onCopyPath,
3006
+ onCopyValue,
3007
+ onPathClick
3008
+ }) {
3009
+ const valueType = getValueType(value);
3010
+ const hasIssue = path ? issuePaths.has(path) : false;
3011
+ const isChanged = path ? changedPaths.has(path) : false;
3012
+ const isRemoved = path ? removedPaths.has(path) : false;
3013
+ const isMatch = path ? matchingPaths.has(path) : false;
3014
+ const isExpandable = valueType === "object" || valueType === "array";
3015
+ const isExpanded = expandedPaths.has(path) || depth < defaultExpandDepth && !expandedPaths.has(path) || isMatch;
3016
+ const entries = isExpandable && value ? Object.entries(value) : [];
3017
+ const handleClick = () => {
3018
+ if (isExpandable) {
3019
+ onToggle(path);
3020
+ } else if (onPathClick) {
3021
+ onPathClick(path, value);
3022
+ }
3023
+ };
3024
+ const highlightKey = (key) => {
3025
+ if (!searchQuery) return key;
3026
+ const idx = key.toLowerCase().indexOf(searchQuery.toLowerCase());
3027
+ if (idx === -1) return key;
3028
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3029
+ key.slice(0, idx),
3030
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-highlight", children: key.slice(idx, idx + searchQuery.length) }),
3031
+ key.slice(idx + searchQuery.length)
3032
+ ] });
3033
+ };
3034
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `stv-node ${depth === 0 ? "stv-root" : ""}`, children: [
3035
+ /* @__PURE__ */ jsxRuntime.jsxs(
3036
+ "div",
3037
+ {
3038
+ className: `stv-row ${isExpandable ? "stv-expandable" : ""} ${hasIssue ? "stv-has-issue" : ""} ${isChanged ? "stv-changed" : ""} ${isRemoved ? "stv-removed" : ""} ${isMatch ? "stv-match" : ""}`,
3039
+ onClick: handleClick,
3040
+ children: [
3041
+ isExpandable ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-toggle", children: isExpanded ? "\u25BC" : "\u25B6" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-toggle stv-toggle-spacer" }),
3042
+ keyName !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "stv-key", children: [
3043
+ highlightKey(keyName),
3044
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-colon", children: ":" })
3045
+ ] }),
3046
+ /* @__PURE__ */ jsxRuntime.jsx(
3047
+ "span",
3048
+ {
3049
+ className: "stv-type-icon",
3050
+ style: { color: typeColors[valueType] },
3051
+ title: valueType,
3052
+ children: typeIcons[valueType]
3053
+ }
3054
+ ),
3055
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `stv-value stv-value-${valueType}`, children: renderValue(value, valueType, isExpanded) }),
3056
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "stv-badges", children: [
3057
+ hasIssue && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-badge stv-badge-issue", title: "Has issue", children: "\u26A0\uFE0F" }),
3058
+ isChanged && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-badge stv-badge-changed", title: "Changed", children: "\u25CF" }),
3059
+ isRemoved && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-badge stv-badge-removed", title: "Removed", children: "\u2715" }),
3060
+ valueType === "nan" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-badge stv-badge-nan", children: "NaN!" }),
3061
+ valueType === "undefined" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-badge stv-badge-undefined", children: "undef" })
3062
+ ] }),
3063
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "stv-actions", children: [
3064
+ /* @__PURE__ */ jsxRuntime.jsx(
3065
+ "button",
3066
+ {
3067
+ className: "stv-action",
3068
+ onClick: (e) => onCopyPath(path, e),
3069
+ title: "Copy path",
3070
+ children: copiedPath === path ? "\u2713" : "\u{1F4CB}"
3071
+ }
3072
+ ),
3073
+ /* @__PURE__ */ jsxRuntime.jsx(
3074
+ "button",
3075
+ {
3076
+ className: "stv-action",
3077
+ onClick: (e) => onCopyValue(value, e),
3078
+ title: "Copy value",
3079
+ children: "\u{1F4C4}"
3080
+ }
3081
+ )
3082
+ ] })
3083
+ ]
3084
+ }
3085
+ ),
3086
+ isExpandable && isExpanded && entries.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "stv-children", children: entries.map(([childKey, childValue]) => {
3087
+ const childPath = path ? `${path}.${childKey}` : childKey;
3088
+ return /* @__PURE__ */ jsxRuntime.jsx(
3089
+ TreeNode,
3090
+ {
3091
+ value: childValue,
3092
+ path: childPath,
3093
+ depth: depth + 1,
3094
+ keyName: childKey,
3095
+ issuePaths,
3096
+ changedPaths,
3097
+ removedPaths,
3098
+ expandedPaths,
3099
+ matchingPaths,
3100
+ defaultExpandDepth,
3101
+ searchQuery,
3102
+ copiedPath,
3103
+ onToggle,
3104
+ onCopyPath,
3105
+ onCopyValue,
3106
+ onPathClick
3107
+ },
3108
+ childPath
3109
+ );
3110
+ }) }),
3111
+ isExpandable && isExpanded && entries.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "stv-children", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-empty", children: "(empty)" }) })
3112
+ ] });
3113
+ }
3114
+ function renderValue(value, valueType, isExpanded) {
3115
+ switch (valueType) {
3116
+ case "null":
3117
+ return "null";
3118
+ case "undefined":
3119
+ return "undefined";
3120
+ case "nan":
3121
+ return "NaN";
3122
+ case "boolean":
3123
+ return String(value);
3124
+ case "number":
3125
+ return String(value);
3126
+ case "string":
3127
+ const str = value;
3128
+ const display = str.length > 50 ? str.slice(0, 50) + "..." : str;
3129
+ return `"${display}"`;
3130
+ case "array":
3131
+ if (!isExpanded) {
3132
+ const arr = value;
3133
+ return `Array(${arr.length})`;
3134
+ }
3135
+ return "";
3136
+ case "object":
3137
+ if (!isExpanded) {
3138
+ const obj = value;
3139
+ const keys = Object.keys(obj);
3140
+ return `{${keys.length} keys}`;
3141
+ }
3142
+ return "";
3143
+ case "function":
3144
+ return "function()";
3145
+ default:
3146
+ return String(value);
3147
+ }
3148
+ }
3149
+ var styles3 = `
3150
+ .state-tree-viewer {
3151
+ background: #1a1a2e;
3152
+ color: #e0e0e0;
3153
+ font-family: system-ui, sans-serif;
3154
+ height: 100%;
3155
+ display: flex;
3156
+ flex-direction: column;
3157
+ }
3158
+
3159
+ .stv-header {
3160
+ display: flex;
3161
+ justify-content: space-between;
3162
+ align-items: center;
3163
+ padding: 0.75rem 1rem;
3164
+ background: #16213e;
3165
+ border-bottom: 1px solid #333;
3166
+ }
3167
+
3168
+ .stv-title {
3169
+ font-weight: 600;
3170
+ color: #00d9ff;
3171
+ }
3172
+
3173
+ .stv-legend {
3174
+ display: flex;
3175
+ gap: 1rem;
3176
+ font-size: 0.75rem;
3177
+ }
3178
+
3179
+ .stv-legend-item {
3180
+ opacity: 0.7;
3181
+ }
3182
+
3183
+ .stv-legend-changed { color: #fbbf24; }
3184
+ .stv-legend-removed { color: #ef4444; }
3185
+ .stv-legend-issue { color: #f472b6; }
3186
+
3187
+ .stv-tree {
3188
+ flex: 1;
3189
+ overflow: auto;
3190
+ padding: 0.75rem;
3191
+ font-family: 'Fira Code', 'Consolas', monospace;
3192
+ font-size: 0.875rem;
3193
+ }
3194
+
3195
+ .stv-node {
3196
+ line-height: 1.6;
3197
+ }
3198
+
3199
+ .stv-row {
3200
+ display: flex;
3201
+ align-items: center;
3202
+ padding: 0.125rem 0.25rem;
3203
+ border-radius: 4px;
3204
+ cursor: default;
3205
+ }
3206
+
3207
+ .stv-row:hover {
3208
+ background: #16213e;
3209
+ }
3210
+
3211
+ .stv-expandable {
3212
+ cursor: pointer;
3213
+ }
3214
+
3215
+ .stv-toggle {
3216
+ width: 1rem;
3217
+ font-size: 0.7rem;
3218
+ color: #666;
3219
+ }
3220
+
3221
+ .stv-toggle-spacer {
3222
+ visibility: hidden;
3223
+ }
3224
+
3225
+ .stv-key {
3226
+ color: #a78bfa;
3227
+ margin-right: 0.375rem;
3228
+ }
3229
+
3230
+ .stv-colon {
3231
+ color: #666;
3232
+ }
3233
+
3234
+ .stv-type-icon {
3235
+ font-size: 0.75rem;
3236
+ width: 1.25rem;
3237
+ text-align: center;
3238
+ opacity: 0.7;
3239
+ }
3240
+
3241
+ .stv-value {
3242
+ flex: 1;
3243
+ }
3244
+
3245
+ .stv-value-null { color: #f472b6; font-style: italic; }
3246
+ .stv-value-undefined { color: #ef4444; font-style: italic; }
3247
+ .stv-value-nan { color: #ef4444; font-weight: bold; }
3248
+ .stv-value-boolean { color: #fb923c; }
3249
+ .stv-value-number { color: #22c55e; }
3250
+ .stv-value-string { color: #fbbf24; }
3251
+ .stv-value-array { color: #a78bfa; }
3252
+ .stv-value-object { color: #60a5fa; }
3253
+ .stv-value-function { color: #888; font-style: italic; }
3254
+
3255
+ .stv-badges {
3256
+ display: flex;
3257
+ gap: 0.25rem;
3258
+ margin-left: 0.5rem;
3259
+ }
3260
+
3261
+ .stv-badge {
3262
+ font-size: 0.7rem;
3263
+ padding: 0 0.25rem;
3264
+ border-radius: 3px;
3265
+ }
802
3266
 
803
- .timeline-scrubber__marker:hover {
804
- transform: scaleY(1.2);
805
- opacity: 1;
806
- }
3267
+ .stv-badge-issue {
3268
+ background: #f472b640;
3269
+ }
807
3270
 
808
- .timeline-scrubber__marker--selected {
809
- opacity: 1;
810
- box-shadow: 0 0 8px currentColor;
811
- }
3271
+ .stv-badge-changed {
3272
+ color: #fbbf24;
3273
+ }
812
3274
 
813
- .timeline-scrubber__position {
814
- position: absolute;
815
- width: 2px;
816
- height: 24px;
817
- top: 0;
818
- background: #fff;
819
- border-radius: 1px;
820
- pointer-events: none;
821
- }
3275
+ .stv-badge-removed {
3276
+ color: #ef4444;
3277
+ background: #ef444430;
3278
+ padding: 0 0.375rem;
3279
+ }
822
3280
 
823
- .timeline-scrubber__slider {
824
- width: 100%;
825
- margin: 0.5rem 0;
826
- cursor: pointer;
827
- }
3281
+ .stv-badge-nan, .stv-badge-undefined {
3282
+ background: #ef444440;
3283
+ color: #fca5a5;
3284
+ padding: 0.125rem 0.375rem;
3285
+ font-weight: 600;
3286
+ }
828
3287
 
829
- .timeline-scrubber__controls {
830
- display: flex;
831
- justify-content: center;
832
- gap: 0.5rem;
833
- margin: 0.5rem 0;
834
- }
3288
+ .stv-actions {
3289
+ display: none;
3290
+ gap: 0.25rem;
3291
+ margin-left: 0.5rem;
3292
+ }
835
3293
 
836
- .timeline-scrubber__button {
837
- padding: 0.5rem 1rem;
838
- border: none;
839
- border-radius: 4px;
840
- background: #16213e;
841
- color: #fff;
842
- cursor: pointer;
843
- font-size: 1rem;
844
- transition: background 0.2s;
845
- }
3294
+ .stv-row:hover .stv-actions {
3295
+ display: flex;
3296
+ }
846
3297
 
847
- .timeline-scrubber__button:hover:not(:disabled) {
848
- background: #1f2b4d;
849
- }
3298
+ .stv-action {
3299
+ padding: 0.125rem 0.375rem;
3300
+ border: none;
3301
+ background: transparent;
3302
+ color: #666;
3303
+ cursor: pointer;
3304
+ font-size: 0.75rem;
3305
+ border-radius: 3px;
3306
+ }
850
3307
 
851
- .timeline-scrubber__button:disabled {
852
- opacity: 0.5;
853
- cursor: not-allowed;
854
- }
3308
+ .stv-action:hover {
3309
+ background: #333;
3310
+ color: #fff;
3311
+ }
855
3312
 
856
- .timeline-scrubber__button--primary {
857
- background: #00d9ff;
858
- color: #000;
859
- }
3313
+ .stv-children {
3314
+ margin-left: 1.25rem;
3315
+ border-left: 1px solid #333;
3316
+ padding-left: 0.5rem;
3317
+ }
860
3318
 
861
- .timeline-scrubber__button--primary:hover:not(:disabled) {
862
- background: #00b8e6;
863
- }
3319
+ .stv-empty {
3320
+ color: #666;
3321
+ font-style: italic;
3322
+ font-size: 0.8rem;
3323
+ padding: 0.25rem 0;
3324
+ }
864
3325
 
865
- .timeline-scrubber__legend {
866
- display: flex;
867
- flex-wrap: wrap;
868
- gap: 1rem;
869
- margin-top: 0.5rem;
870
- font-size: 0.75rem;
871
- }
3326
+ .stv-has-issue {
3327
+ background: #f472b620 !important;
3328
+ }
872
3329
 
873
- .timeline-scrubber__legend-item {
874
- display: flex;
875
- align-items: center;
876
- gap: 0.25rem;
877
- color: #888;
878
- }
3330
+ .stv-changed {
3331
+ background: #fbbf2420 !important;
3332
+ }
879
3333
 
880
- .timeline-scrubber__legend-dot {
881
- width: 8px;
882
- height: 8px;
883
- border-radius: 50%;
884
- }
885
- ` })
886
- ] });
887
- }
888
- var DashboardContext = React2__default.default.createContext(null);
3334
+ .stv-removed {
3335
+ background: #ef444420 !important;
3336
+ text-decoration: line-through;
3337
+ opacity: 0.7;
3338
+ }
3339
+
3340
+ .stv-match .stv-key {
3341
+ background: #fbbf2440;
3342
+ padding: 0 0.25rem;
3343
+ border-radius: 2px;
3344
+ }
3345
+
3346
+ .stv-highlight {
3347
+ background: #fbbf2480;
3348
+ padding: 0 0.125rem;
3349
+ border-radius: 2px;
3350
+ }
3351
+ `;
3352
+ var DashboardContext = React4__default.default.createContext(null);
889
3353
  function DashboardProvider({
890
3354
  serverUrl = "http://localhost:8080",
891
3355
  children
892
3356
  }) {
893
- const [state, setState] = React2.useState({
3357
+ const [state, setState] = React4.useState({
894
3358
  serverUrl,
895
3359
  sessions: [],
896
3360
  currentSession: null,
@@ -933,7 +3397,7 @@ function DashboardProvider({
933
3397
  if (!state.currentSession) return;
934
3398
  await selectSession(state.currentSession.id);
935
3399
  };
936
- React2.useEffect(() => {
3400
+ React4.useEffect(() => {
937
3401
  loadSessions();
938
3402
  }, [serverUrl]);
939
3403
  const value = {
@@ -946,17 +3410,17 @@ function DashboardProvider({
946
3410
  return /* @__PURE__ */ jsxRuntime.jsx(DashboardContext.Provider, { value, children });
947
3411
  }
948
3412
  function useDashboard() {
949
- const context = React2__default.default.useContext(DashboardContext);
3413
+ const context = React4__default.default.useContext(DashboardContext);
950
3414
  if (!context) {
951
3415
  throw new Error("useDashboard must be used within a DashboardProvider");
952
3416
  }
953
3417
  return context;
954
3418
  }
955
3419
  function usePlayback(timeline) {
956
- const [currentIndex, setCurrentIndex] = React2.useState(0);
957
- const [isPlaying, setIsPlaying] = React2.useState(false);
958
- const [playbackSpeed, setPlaybackSpeed] = React2.useState(1);
959
- React2.useEffect(() => {
3420
+ const [currentIndex, setCurrentIndex] = React4.useState(0);
3421
+ const [isPlaying, setIsPlaying] = React4.useState(false);
3422
+ const [playbackSpeed, setPlaybackSpeed] = React4.useState(1);
3423
+ React4.useEffect(() => {
960
3424
  if (!isPlaying || timeline.length === 0) return;
961
3425
  const interval = setInterval(() => {
962
3426
  setCurrentIndex((i) => {
@@ -998,189 +3462,657 @@ function DashboardContent() {
998
3462
  loadSessions
999
3463
  } = useDashboard();
1000
3464
  const playback = usePlayback(timeline);
1001
- React2__default.default.useEffect(() => {
3465
+ const [mainTab, setMainTab] = React4.useState("issues");
3466
+ const [detailTab, setDetailTab] = React4.useState("tree");
3467
+ const [autoRefresh, setAutoRefresh] = React4.useState(true);
3468
+ const [searchQuery, setSearchQuery] = React4.useState("");
3469
+ const analyzer2 = React4.useMemo(() => new StateAnalyzer(), []);
3470
+ const issues = React4.useMemo(() => {
3471
+ return analyzer2.analyzeTimeline(timeline);
3472
+ }, [analyzer2, timeline]);
3473
+ const dependencyGraph = React4.useMemo(() => {
3474
+ return analyzer2.buildDependencyGraph(timeline);
3475
+ }, [analyzer2, timeline]);
3476
+ const issuePaths = React4.useMemo(() => {
3477
+ return new Set(issues.filter((i) => i.path).map((i) => i.path));
3478
+ }, [issues]);
3479
+ const changedPaths = React4.useMemo(() => {
3480
+ if (!selectedMutation?.diff) return /* @__PURE__ */ new Set();
3481
+ return new Set(selectedMutation.diff.map((d) => d.path));
3482
+ }, [selectedMutation]);
3483
+ const removedPaths = React4.useMemo(() => {
3484
+ if (!selectedMutation?.diff) return /* @__PURE__ */ new Set();
3485
+ return new Set(
3486
+ selectedMutation.diff.filter((d) => d.operation === "REMOVE").map((d) => d.path)
3487
+ );
3488
+ }, [selectedMutation]);
3489
+ const issueCounts = React4.useMemo(() => {
3490
+ const counts = { critical: 0, warning: 0, info: 0 };
3491
+ for (const issue of issues) {
3492
+ counts[issue.severity]++;
3493
+ }
3494
+ return counts;
3495
+ }, [issues]);
3496
+ React4.useEffect(() => {
1002
3497
  if (playback.currentMutation) {
1003
3498
  selectMutation(playback.currentMutation);
1004
3499
  }
1005
3500
  }, [playback.currentMutation, selectMutation]);
1006
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "surgeon-dashboard", children: [
1007
- /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "surgeon-dashboard__header", children: [
1008
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "surgeon-dashboard__logo", children: "\u{1F52C} State Surgeon" }),
1009
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "surgeon-dashboard__session-select", children: [
1010
- /* @__PURE__ */ jsxRuntime.jsxs(
1011
- "select",
3501
+ React4.useEffect(() => {
3502
+ if (!autoRefresh) return;
3503
+ const interval = setInterval(loadSessions, 5e3);
3504
+ return () => clearInterval(interval);
3505
+ }, [autoRefresh, loadSessions]);
3506
+ React4.useEffect(() => {
3507
+ const handleKeyDown = (e) => {
3508
+ if (e.target instanceof HTMLInputElement) return;
3509
+ switch (e.key) {
3510
+ case "ArrowLeft":
3511
+ playback.stepBackward();
3512
+ break;
3513
+ case "ArrowRight":
3514
+ playback.stepForward();
3515
+ break;
3516
+ case " ":
3517
+ e.preventDefault();
3518
+ playback.isPlaying ? playback.pause() : playback.play();
3519
+ break;
3520
+ case "1":
3521
+ setMainTab("issues");
3522
+ break;
3523
+ case "2":
3524
+ setMainTab("timeline");
3525
+ break;
3526
+ case "3":
3527
+ setMainTab("graph");
3528
+ break;
3529
+ }
3530
+ };
3531
+ window.addEventListener("keydown", handleKeyDown);
3532
+ return () => window.removeEventListener("keydown", handleKeyDown);
3533
+ }, [playback]);
3534
+ const handleJumpToMutation = (mutationId, index) => {
3535
+ playback.goTo(index);
3536
+ const mutation = timeline[index];
3537
+ if (mutation) {
3538
+ selectMutation(mutation);
3539
+ }
3540
+ setMainTab("timeline");
3541
+ };
3542
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "surgeon-dashboard-v2", children: [
3543
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "sd2-header", children: [
3544
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-header-left", children: [
3545
+ /* @__PURE__ */ jsxRuntime.jsxs("h1", { className: "sd2-logo", children: [
3546
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd2-logo-icon", children: "\u{1F52C}" }),
3547
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd2-logo-text", children: "State Surgeon" }),
3548
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd2-version", children: "v2.0" })
3549
+ ] }),
3550
+ issues.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-issue-badges", children: [
3551
+ issueCounts.critical > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sd2-badge sd2-badge-critical", children: [
3552
+ "\u{1F534} ",
3553
+ issueCounts.critical
3554
+ ] }),
3555
+ issueCounts.warning > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sd2-badge sd2-badge-warning", children: [
3556
+ "\u{1F7E1} ",
3557
+ issueCounts.warning
3558
+ ] }),
3559
+ issueCounts.info > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sd2-badge sd2-badge-info", children: [
3560
+ "\u{1F7E2} ",
3561
+ issueCounts.info
3562
+ ] })
3563
+ ] })
3564
+ ] }),
3565
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd2-header-center", children: /* @__PURE__ */ jsxRuntime.jsxs(
3566
+ "select",
3567
+ {
3568
+ value: currentSession?.id || "",
3569
+ onChange: (e) => selectSession(e.target.value),
3570
+ disabled: isLoading,
3571
+ className: "sd2-session-select",
3572
+ children: [
3573
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select a session..." }),
3574
+ sessions.map((session) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: session.id, children: [
3575
+ session.appId,
3576
+ " \u2022 ",
3577
+ session.id.slice(0, 8),
3578
+ "... \u2022 ",
3579
+ session.mutationCount,
3580
+ " mutations"
3581
+ ] }, session.id))
3582
+ ]
3583
+ }
3584
+ ) }),
3585
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-header-right", children: [
3586
+ /* @__PURE__ */ jsxRuntime.jsx(
3587
+ "input",
1012
3588
  {
1013
- value: currentSession?.id || "",
1014
- onChange: (e) => selectSession(e.target.value),
1015
- disabled: isLoading,
1016
- children: [
1017
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select a session..." }),
1018
- sessions.map((session) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: session.id, children: [
1019
- session.appId,
1020
- " - ",
1021
- session.id.slice(0, 16),
1022
- "... (",
1023
- session.mutationCount,
1024
- " mutations)"
1025
- ] }, session.id))
1026
- ]
3589
+ type: "text",
3590
+ placeholder: "\u{1F50D} Search state paths...",
3591
+ value: searchQuery,
3592
+ onChange: (e) => setSearchQuery(e.target.value),
3593
+ className: "sd2-search"
1027
3594
  }
1028
3595
  ),
1029
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: loadSessions, disabled: isLoading, children: "\u{1F504}" })
3596
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "sd2-auto-refresh", children: [
3597
+ /* @__PURE__ */ jsxRuntime.jsx(
3598
+ "input",
3599
+ {
3600
+ type: "checkbox",
3601
+ checked: autoRefresh,
3602
+ onChange: (e) => setAutoRefresh(e.target.checked)
3603
+ }
3604
+ ),
3605
+ "Auto"
3606
+ ] }),
3607
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: loadSessions, disabled: isLoading, className: "sd2-refresh-btn", children: isLoading ? "\u23F3" : "\u{1F504}" })
1030
3608
  ] })
1031
3609
  ] }),
1032
- error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "surgeon-dashboard__error", children: /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
1033
- "Error: ",
3610
+ error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-error", children: [
3611
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\u274C" }),
3612
+ " ",
1034
3613
  error
1035
- ] }) }),
1036
- isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "surgeon-dashboard__loading", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Loading..." }) }),
1037
- currentSession && !isLoading && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "surgeon-dashboard__main", children: [
1038
- /* @__PURE__ */ jsxRuntime.jsx("section", { className: "surgeon-dashboard__timeline", children: /* @__PURE__ */ jsxRuntime.jsx(
1039
- TimelineScrubber,
1040
- {
1041
- mutations: timeline,
1042
- selectedIndex: playback.currentIndex,
1043
- onSelect: (index, mutation) => {
1044
- playback.goTo(index);
1045
- selectMutation(mutation);
1046
- },
1047
- isPlaying: playback.isPlaying,
1048
- onPlay: playback.play,
1049
- onPause: playback.pause,
1050
- onStepForward: playback.stepForward,
1051
- onStepBackward: playback.stepBackward
1052
- }
1053
- ) }),
1054
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "surgeon-dashboard__content", children: [
1055
- /* @__PURE__ */ jsxRuntime.jsx("section", { className: "surgeon-dashboard__diff", children: selectedMutation && /* @__PURE__ */ jsxRuntime.jsx(
1056
- StateDiffViewer,
3614
+ ] }),
3615
+ !currentSession && !isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd2-welcome", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-welcome-content", children: [
3616
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd2-welcome-icon", children: "\u{1F52C}" }),
3617
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "State Surgeon v2.0" }),
3618
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Forensic debugging platform for JavaScript applications" }),
3619
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-features", children: [
3620
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-feature", children: [
3621
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd2-feature-icon", children: "\u{1F6A8}" }),
3622
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Issue Detection" }),
3623
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Find state loss, invalid values, broken invariants" })
3624
+ ] }),
3625
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-feature", children: [
3626
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd2-feature-icon", children: "\u{1F333}" }),
3627
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "State Tree" }),
3628
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Visual tree with inline anomaly badges" })
3629
+ ] }),
3630
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-feature", children: [
3631
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd2-feature-icon", children: "\u{1F517}" }),
3632
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Dependency Graph" }),
3633
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Detect hidden coupling between components" })
3634
+ ] }),
3635
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-feature", children: [
3636
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd2-feature-icon", children: "\u23F1\uFE0F" }),
3637
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Timeline Analysis" }),
3638
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "See exactly what your code did" })
3639
+ ] })
3640
+ ] }),
3641
+ sessions.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-session-list", children: [
3642
+ /* @__PURE__ */ jsxRuntime.jsxs("h3", { children: [
3643
+ "Sessions (",
3644
+ sessions.length,
3645
+ ")"
3646
+ ] }),
3647
+ sessions.map((session) => /* @__PURE__ */ jsxRuntime.jsxs(
3648
+ "button",
1057
3649
  {
1058
- previousState: selectedMutation.previousState,
1059
- nextState: selectedMutation.nextState,
1060
- diff: selectedMutation.diff
1061
- }
1062
- ) }),
1063
- /* @__PURE__ */ jsxRuntime.jsx("section", { className: "surgeon-dashboard__inspector", children: /* @__PURE__ */ jsxRuntime.jsx(MutationInspector, { mutation: selectedMutation }) })
3650
+ className: "sd2-session-card",
3651
+ onClick: () => selectSession(session.id),
3652
+ children: [
3653
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd2-session-app", children: session.appId }),
3654
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sd2-session-count", children: [
3655
+ session.mutationCount,
3656
+ " mutations"
3657
+ ] })
3658
+ ]
3659
+ },
3660
+ session.id
3661
+ ))
1064
3662
  ] })
3663
+ ] }) }),
3664
+ isLoading && !currentSession && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-loading", children: [
3665
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd2-spinner" }),
3666
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Loading sessions..." })
1065
3667
  ] }),
1066
- !currentSession && !isLoading && sessions.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "surgeon-dashboard__empty", children: [
1067
- /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "No Sessions Yet" }),
1068
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Connect your application to start capturing mutations." }),
1069
- /* @__PURE__ */ jsxRuntime.jsx("pre", { children: `import { StateSurgeonClient, instrumentReact } from 'state-surgeon/instrument';
1070
-
1071
- // Initialize the client
1072
- const client = new StateSurgeonClient({
1073
- serverUrl: 'ws://localhost:8081',
1074
- appId: 'my-app',
1075
- });
1076
-
1077
- // Instrument React
1078
- instrumentReact(React);` })
3668
+ currentSession && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-main", children: [
3669
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-left-panel", children: [
3670
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-tabs", children: [
3671
+ /* @__PURE__ */ jsxRuntime.jsxs(
3672
+ "button",
3673
+ {
3674
+ className: `sd2-tab ${mainTab === "issues" ? "active" : ""}`,
3675
+ onClick: () => setMainTab("issues"),
3676
+ children: [
3677
+ "\u{1F6A8} Issues ",
3678
+ issues.length > 0 && `(${issues.length})`
3679
+ ]
3680
+ }
3681
+ ),
3682
+ /* @__PURE__ */ jsxRuntime.jsx(
3683
+ "button",
3684
+ {
3685
+ className: `sd2-tab ${mainTab === "timeline" ? "active" : ""}`,
3686
+ onClick: () => setMainTab("timeline"),
3687
+ children: "\u{1F4CA} Timeline"
3688
+ }
3689
+ ),
3690
+ /* @__PURE__ */ jsxRuntime.jsxs(
3691
+ "button",
3692
+ {
3693
+ className: `sd2-tab ${mainTab === "graph" ? "active" : ""}`,
3694
+ onClick: () => setMainTab("graph"),
3695
+ children: [
3696
+ "\u{1F517} Graph ",
3697
+ dependencyGraph.couplings.length > 0 && `(${dependencyGraph.couplings.length})`
3698
+ ]
3699
+ }
3700
+ )
3701
+ ] }),
3702
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-tab-content", children: [
3703
+ mainTab === "issues" && /* @__PURE__ */ jsxRuntime.jsx(
3704
+ IssuesPanel,
3705
+ {
3706
+ issues,
3707
+ onJumpToMutation: handleJumpToMutation
3708
+ }
3709
+ ),
3710
+ mainTab === "timeline" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd2-timeline-panel", children: /* @__PURE__ */ jsxRuntime.jsx(
3711
+ TimelineScrubber,
3712
+ {
3713
+ mutations: timeline,
3714
+ selectedIndex: playback.currentIndex,
3715
+ onSelect: (index, mutation) => {
3716
+ playback.goTo(index);
3717
+ selectMutation(mutation);
3718
+ },
3719
+ isPlaying: playback.isPlaying,
3720
+ onPlay: playback.play,
3721
+ onPause: playback.pause,
3722
+ onStepForward: playback.stepForward,
3723
+ onStepBackward: playback.stepBackward,
3724
+ playbackSpeed: playback.playbackSpeed,
3725
+ onSpeedChange: playback.setSpeed
3726
+ }
3727
+ ) }),
3728
+ mainTab === "graph" && /* @__PURE__ */ jsxRuntime.jsx(
3729
+ DependencyGraph,
3730
+ {
3731
+ graph: dependencyGraph,
3732
+ onPathSelect: (path) => setSearchQuery(path)
3733
+ }
3734
+ )
3735
+ ] })
3736
+ ] }),
3737
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-right-panel", children: [
3738
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-tabs", children: [
3739
+ /* @__PURE__ */ jsxRuntime.jsx(
3740
+ "button",
3741
+ {
3742
+ className: `sd2-tab ${detailTab === "tree" ? "active" : ""}`,
3743
+ onClick: () => setDetailTab("tree"),
3744
+ children: "\u{1F333} Tree"
3745
+ }
3746
+ ),
3747
+ /* @__PURE__ */ jsxRuntime.jsx(
3748
+ "button",
3749
+ {
3750
+ className: `sd2-tab ${detailTab === "diff" ? "active" : ""}`,
3751
+ onClick: () => setDetailTab("diff"),
3752
+ children: "\u{1F4CA} Diff"
3753
+ }
3754
+ ),
3755
+ /* @__PURE__ */ jsxRuntime.jsx(
3756
+ "button",
3757
+ {
3758
+ className: `sd2-tab ${detailTab === "inspector" ? "active" : ""}`,
3759
+ onClick: () => setDetailTab("inspector"),
3760
+ children: "\u{1F50D} Details"
3761
+ }
3762
+ )
3763
+ ] }),
3764
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-tab-content", children: [
3765
+ detailTab === "tree" && selectedMutation && /* @__PURE__ */ jsxRuntime.jsx(
3766
+ StateTreeViewer,
3767
+ {
3768
+ state: selectedMutation.nextState,
3769
+ issuePaths,
3770
+ changedPaths,
3771
+ removedPaths,
3772
+ searchQuery
3773
+ }
3774
+ ),
3775
+ detailTab === "diff" && selectedMutation && /* @__PURE__ */ jsxRuntime.jsx(
3776
+ StateDiffViewer,
3777
+ {
3778
+ previousState: selectedMutation.previousState,
3779
+ nextState: selectedMutation.nextState,
3780
+ diff: selectedMutation.diff
3781
+ }
3782
+ ),
3783
+ detailTab === "inspector" && /* @__PURE__ */ jsxRuntime.jsx(MutationInspector, { mutation: selectedMutation }),
3784
+ !selectedMutation && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd2-panel-empty", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Select a mutation to view state details" }) })
3785
+ ] })
3786
+ ] })
1079
3787
  ] }),
1080
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
1081
- .surgeon-dashboard {
1082
- min-height: 100vh;
1083
- background: #0f0f23;
1084
- color: #e0e0e0;
1085
- font-family: system-ui, -apple-system, sans-serif;
1086
- }
3788
+ /* @__PURE__ */ jsxRuntime.jsxs("footer", { className: "sd2-footer", children: [
3789
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "State Surgeon v2.0" }),
3790
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd2-sep", children: "\u2022" }),
3791
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\u2190 \u2192 navigate \u2022 Space play/pause \u2022 1-3 switch tabs" })
3792
+ ] }),
3793
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: dashboardStyles })
3794
+ ] });
3795
+ }
3796
+ var dashboardStyles = `
3797
+ .surgeon-dashboard-v2 {
3798
+ min-height: 100vh;
3799
+ display: flex;
3800
+ flex-direction: column;
3801
+ background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 100%);
3802
+ color: #e0e0e0;
3803
+ font-family: system-ui, -apple-system, sans-serif;
3804
+ }
1087
3805
 
1088
- .surgeon-dashboard__header {
1089
- display: flex;
1090
- justify-content: space-between;
1091
- align-items: center;
1092
- padding: 1rem 2rem;
1093
- background: #1a1a2e;
1094
- border-bottom: 1px solid #333;
1095
- }
3806
+ /* Header */
3807
+ .sd2-header {
3808
+ display: flex;
3809
+ justify-content: space-between;
3810
+ align-items: center;
3811
+ padding: 0.75rem 1.5rem;
3812
+ background: rgba(22, 33, 62, 0.95);
3813
+ backdrop-filter: blur(10px);
3814
+ border-bottom: 1px solid #333;
3815
+ position: sticky;
3816
+ top: 0;
3817
+ z-index: 100;
3818
+ flex-wrap: wrap;
3819
+ gap: 0.75rem;
3820
+ }
1096
3821
 
1097
- .surgeon-dashboard__logo {
1098
- margin: 0;
1099
- font-size: 1.5rem;
1100
- color: #00d9ff;
1101
- }
3822
+ .sd2-header-left, .sd2-header-right {
3823
+ display: flex;
3824
+ align-items: center;
3825
+ gap: 1rem;
3826
+ }
1102
3827
 
1103
- .surgeon-dashboard__session-select {
1104
- display: flex;
1105
- gap: 0.5rem;
1106
- }
3828
+ .sd2-logo {
3829
+ display: flex;
3830
+ align-items: center;
3831
+ gap: 0.5rem;
3832
+ margin: 0;
3833
+ font-size: 1.125rem;
3834
+ }
1107
3835
 
1108
- .surgeon-dashboard__session-select select {
1109
- padding: 0.5rem 1rem;
1110
- border: 1px solid #333;
1111
- border-radius: 4px;
1112
- background: #16213e;
1113
- color: #e0e0e0;
1114
- min-width: 300px;
1115
- }
3836
+ .sd2-logo-icon { font-size: 1.25rem; }
1116
3837
 
1117
- .surgeon-dashboard__session-select button {
1118
- padding: 0.5rem 1rem;
1119
- border: 1px solid #333;
1120
- border-radius: 4px;
1121
- background: #16213e;
1122
- color: #e0e0e0;
1123
- cursor: pointer;
1124
- }
3838
+ .sd2-logo-text {
3839
+ background: linear-gradient(135deg, #00d9ff, #a78bfa);
3840
+ -webkit-background-clip: text;
3841
+ -webkit-text-fill-color: transparent;
3842
+ background-clip: text;
3843
+ font-weight: 700;
3844
+ }
1125
3845
 
1126
- .surgeon-dashboard__session-select button:hover {
1127
- background: #1f2b4d;
1128
- }
3846
+ .sd2-version {
3847
+ font-size: 0.65rem;
3848
+ padding: 0.15rem 0.35rem;
3849
+ background: linear-gradient(135deg, #22c55e, #06b6d4);
3850
+ border-radius: 4px;
3851
+ color: #000;
3852
+ font-weight: 600;
3853
+ }
1129
3854
 
1130
- .surgeon-dashboard__error {
1131
- padding: 1rem 2rem;
1132
- background: rgba(239, 68, 68, 0.1);
1133
- border-bottom: 1px solid #ef4444;
1134
- color: #fca5a5;
1135
- }
3855
+ .sd2-issue-badges {
3856
+ display: flex;
3857
+ gap: 0.5rem;
3858
+ }
1136
3859
 
1137
- .surgeon-dashboard__loading {
1138
- padding: 2rem;
1139
- text-align: center;
1140
- color: #888;
1141
- }
3860
+ .sd2-badge {
3861
+ padding: 0.25rem 0.5rem;
3862
+ border-radius: 12px;
3863
+ font-size: 0.75rem;
3864
+ font-weight: 600;
3865
+ }
1142
3866
 
1143
- .surgeon-dashboard__main {
1144
- padding: 1rem 2rem;
1145
- }
3867
+ .sd2-badge-critical { background: #ef444430; color: #fca5a5; }
3868
+ .sd2-badge-warning { background: #fbbf2430; color: #fcd34d; }
3869
+ .sd2-badge-info { background: #22c55e30; color: #86efac; }
1146
3870
 
1147
- .surgeon-dashboard__timeline {
1148
- margin-bottom: 1rem;
1149
- }
3871
+ .sd2-session-select {
3872
+ padding: 0.5rem 1rem;
3873
+ min-width: 300px;
3874
+ border: 1px solid #333;
3875
+ border-radius: 8px;
3876
+ background: #0f0f23;
3877
+ color: #e0e0e0;
3878
+ font-size: 0.875rem;
3879
+ cursor: pointer;
3880
+ }
1150
3881
 
1151
- .surgeon-dashboard__content {
1152
- display: grid;
1153
- grid-template-columns: 2fr 1fr;
1154
- gap: 1rem;
1155
- }
3882
+ .sd2-search {
3883
+ padding: 0.5rem 1rem;
3884
+ width: 200px;
3885
+ border: 1px solid #333;
3886
+ border-radius: 8px;
3887
+ background: #0f0f23;
3888
+ color: #e0e0e0;
3889
+ font-size: 0.875rem;
3890
+ }
1156
3891
 
1157
- .surgeon-dashboard__empty {
1158
- padding: 4rem 2rem;
1159
- text-align: center;
1160
- }
3892
+ .sd2-auto-refresh {
3893
+ display: flex;
3894
+ align-items: center;
3895
+ gap: 0.375rem;
3896
+ font-size: 0.8rem;
3897
+ color: #888;
3898
+ cursor: pointer;
3899
+ }
1161
3900
 
1162
- .surgeon-dashboard__empty h2 {
1163
- color: #888;
1164
- margin-bottom: 1rem;
1165
- }
3901
+ .sd2-refresh-btn {
3902
+ padding: 0.375rem 0.625rem;
3903
+ border: 1px solid #333;
3904
+ border-radius: 6px;
3905
+ background: transparent;
3906
+ color: #e0e0e0;
3907
+ cursor: pointer;
3908
+ font-size: 1rem;
3909
+ }
1166
3910
 
1167
- .surgeon-dashboard__empty pre {
1168
- display: inline-block;
1169
- text-align: left;
1170
- padding: 1.5rem;
1171
- background: #16213e;
1172
- border-radius: 8px;
1173
- font-size: 0.875rem;
1174
- }
3911
+ .sd2-refresh-btn:hover { background: #16213e; }
1175
3912
 
1176
- @media (max-width: 1024px) {
1177
- .surgeon-dashboard__content {
1178
- grid-template-columns: 1fr;
1179
- }
1180
- }
1181
- ` })
1182
- ] });
1183
- }
3913
+ .sd2-error {
3914
+ padding: 0.75rem 1.5rem;
3915
+ background: rgba(239, 68, 68, 0.1);
3916
+ border-bottom: 1px solid #ef4444;
3917
+ color: #fca5a5;
3918
+ }
3919
+
3920
+ /* Welcome */
3921
+ .sd2-welcome {
3922
+ flex: 1;
3923
+ display: flex;
3924
+ align-items: center;
3925
+ justify-content: center;
3926
+ padding: 2rem;
3927
+ }
3928
+
3929
+ .sd2-welcome-content {
3930
+ text-align: center;
3931
+ max-width: 700px;
3932
+ }
3933
+
3934
+ .sd2-welcome-icon {
3935
+ font-size: 4rem;
3936
+ margin-bottom: 1rem;
3937
+ animation: float 3s ease-in-out infinite;
3938
+ }
3939
+
3940
+ @keyframes float {
3941
+ 0%, 100% { transform: translateY(0); }
3942
+ 50% { transform: translateY(-10px); }
3943
+ }
3944
+
3945
+ .sd2-welcome h2 {
3946
+ margin: 0 0 0.5rem;
3947
+ font-size: 1.75rem;
3948
+ background: linear-gradient(135deg, #00d9ff, #a78bfa);
3949
+ -webkit-background-clip: text;
3950
+ -webkit-text-fill-color: transparent;
3951
+ background-clip: text;
3952
+ }
3953
+
3954
+ .sd2-welcome > div > p {
3955
+ color: #888;
3956
+ margin-bottom: 2rem;
3957
+ }
3958
+
3959
+ .sd2-features {
3960
+ display: grid;
3961
+ grid-template-columns: repeat(2, 1fr);
3962
+ gap: 1rem;
3963
+ margin-bottom: 2rem;
3964
+ }
3965
+
3966
+ .sd2-feature {
3967
+ display: flex;
3968
+ flex-direction: column;
3969
+ align-items: center;
3970
+ padding: 1.25rem;
3971
+ background: #16213e;
3972
+ border-radius: 12px;
3973
+ text-align: center;
3974
+ }
3975
+
3976
+ .sd2-feature-icon {
3977
+ font-size: 2rem;
3978
+ margin-bottom: 0.5rem;
3979
+ }
3980
+
3981
+ .sd2-feature strong {
3982
+ color: #00d9ff;
3983
+ margin-bottom: 0.25rem;
3984
+ }
3985
+
3986
+ .sd2-feature span:last-child {
3987
+ font-size: 0.8rem;
3988
+ color: #888;
3989
+ }
3990
+
3991
+ .sd2-session-list h3 {
3992
+ color: #00d9ff;
3993
+ margin-bottom: 0.75rem;
3994
+ }
3995
+
3996
+ .sd2-session-card {
3997
+ display: flex;
3998
+ justify-content: space-between;
3999
+ width: 100%;
4000
+ padding: 0.875rem 1.25rem;
4001
+ margin-bottom: 0.5rem;
4002
+ background: #16213e;
4003
+ border: 1px solid #333;
4004
+ border-radius: 8px;
4005
+ cursor: pointer;
4006
+ color: inherit;
4007
+ transition: all 0.2s;
4008
+ }
4009
+
4010
+ .sd2-session-card:hover {
4011
+ border-color: #00d9ff;
4012
+ background: #1a2744;
4013
+ }
4014
+
4015
+ .sd2-session-app { color: #a78bfa; font-weight: 600; }
4016
+ .sd2-session-count { color: #00d9ff; }
4017
+
4018
+ .sd2-loading {
4019
+ flex: 1;
4020
+ display: flex;
4021
+ flex-direction: column;
4022
+ align-items: center;
4023
+ justify-content: center;
4024
+ gap: 1rem;
4025
+ }
4026
+
4027
+ .sd2-spinner {
4028
+ width: 36px;
4029
+ height: 36px;
4030
+ border: 3px solid #333;
4031
+ border-top-color: #00d9ff;
4032
+ border-radius: 50%;
4033
+ animation: spin 1s linear infinite;
4034
+ }
4035
+
4036
+ @keyframes spin { to { transform: rotate(360deg); } }
4037
+
4038
+ /* Main content */
4039
+ .sd2-main {
4040
+ flex: 1;
4041
+ display: grid;
4042
+ grid-template-columns: 1fr 1fr;
4043
+ gap: 1rem;
4044
+ padding: 1rem;
4045
+ min-height: 0;
4046
+ }
4047
+
4048
+ @media (max-width: 1200px) {
4049
+ .sd2-main { grid-template-columns: 1fr; }
4050
+ }
4051
+
4052
+ .sd2-left-panel, .sd2-right-panel {
4053
+ display: flex;
4054
+ flex-direction: column;
4055
+ background: #1a1a2e;
4056
+ border-radius: 12px;
4057
+ overflow: hidden;
4058
+ min-height: 500px;
4059
+ }
4060
+
4061
+ .sd2-tabs {
4062
+ display: flex;
4063
+ background: #16213e;
4064
+ border-bottom: 1px solid #333;
4065
+ }
4066
+
4067
+ .sd2-tab {
4068
+ flex: 1;
4069
+ padding: 0.75rem 1rem;
4070
+ border: none;
4071
+ background: transparent;
4072
+ color: #888;
4073
+ cursor: pointer;
4074
+ font-size: 0.875rem;
4075
+ transition: all 0.2s;
4076
+ border-bottom: 2px solid transparent;
4077
+ }
4078
+
4079
+ .sd2-tab:hover { color: #ccc; background: #1a2744; }
4080
+
4081
+ .sd2-tab.active {
4082
+ color: #00d9ff;
4083
+ background: #1a1a2e;
4084
+ border-bottom-color: #00d9ff;
4085
+ }
4086
+
4087
+ .sd2-tab-content {
4088
+ flex: 1;
4089
+ overflow: auto;
4090
+ }
4091
+
4092
+ .sd2-timeline-panel {
4093
+ padding: 1rem;
4094
+ }
4095
+
4096
+ .sd2-panel-empty {
4097
+ height: 100%;
4098
+ display: flex;
4099
+ align-items: center;
4100
+ justify-content: center;
4101
+ color: #666;
4102
+ }
4103
+
4104
+ /* Footer */
4105
+ .sd2-footer {
4106
+ padding: 0.625rem 1.5rem;
4107
+ background: rgba(22, 33, 62, 0.5);
4108
+ border-top: 1px solid #333;
4109
+ font-size: 0.75rem;
4110
+ color: #666;
4111
+ text-align: center;
4112
+ }
4113
+
4114
+ .sd2-sep { margin: 0 0.5rem; }
4115
+ `;
1184
4116
 
1185
4117
  exports.DashboardProvider = DashboardProvider;
1186
4118
  exports.MutationInspector = MutationInspector;