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