state-surgeon 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 React2, { 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,141 +547,404 @@ 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;
464
713
  }
465
714
 
466
- .state-diff-viewer__change-type {
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;
724
+ }
725
+
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;
477
773
  }
478
774
 
479
- .state-diff-viewer__value--old {
480
- background: rgba(239, 68, 68, 0.2);
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;
781
+ }
782
+
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;
796
+ }
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;
492
898
  }
493
899
  ` })
494
900
  ] });
495
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]"
940
+ ] });
941
+ }
942
+ if (typeof value === "object") {
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
+ }
496
948
  function StateTree({
497
949
  value,
498
950
  path,
@@ -508,19 +960,29 @@ function StateTree({
508
960
  return /* @__PURE__ */ jsx("span", { className: "state-tree__null", children: "null" });
509
961
  }
510
962
  if (value === void 0) {
511
- return /* @__PURE__ */ jsx("span", { className: "state-tree__undefined", children: "undefined" });
963
+ return /* @__PURE__ */ jsxs("span", { className: "state-tree__undefined", children: [
964
+ "undefined",
965
+ /* @__PURE__ */ jsx("span", { className: "state-tree__anomaly", children: "\u26A0\uFE0F" })
966
+ ] });
512
967
  }
513
968
  if (typeof value === "boolean") {
514
969
  return /* @__PURE__ */ jsx("span", { className: "state-tree__boolean", children: String(value) });
515
970
  }
516
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
+ ] });
977
+ }
517
978
  return /* @__PURE__ */ jsx("span", { className: `state-tree__number ${isChanged ? "state-tree--changed" : ""}`, children: String(value) });
518
979
  }
519
980
  if (typeof value === "string") {
520
981
  return /* @__PURE__ */ jsxs("span", { className: `state-tree__string ${isChanged ? "state-tree--changed" : ""}`, children: [
521
982
  '"',
522
983
  value,
523
- '"'
984
+ '"',
985
+ value === "" && /* @__PURE__ */ jsx("span", { className: "state-tree__anomaly", children: "empty" })
524
986
  ] });
525
987
  }
526
988
  if (Array.isArray(value)) {
@@ -565,40 +1027,31 @@ function StateTree({
565
1027
  entries.length,
566
1028
  ")"
567
1029
  ] }),
568
- isExpanded && /* @__PURE__ */ jsx("div", { className: "state-tree__children", children: entries.map(([key, val]) => /* @__PURE__ */ jsxs("div", { className: "state-tree__item", children: [
569
- /* @__PURE__ */ jsxs("span", { className: "state-tree__key", children: [
570
- key,
571
- ":"
572
- ] }),
573
- /* @__PURE__ */ jsx(
574
- StateTree,
575
- {
576
- value: val,
577
- path: path ? `${path}.${key}` : key,
578
- changedPaths,
579
- defaultExpandDepth,
580
- depth: depth + 1,
581
- changeType
582
- }
583
- )
584
- ] }, key)) })
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
+ }) })
585
1051
  ] });
586
1052
  }
587
1053
  return /* @__PURE__ */ jsx("span", { children: String(value) });
588
1054
  }
589
- function formatValue(value) {
590
- if (value === void 0) return "undefined";
591
- if (value === null) return "null";
592
- 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]";
598
- }
599
- }
600
- return String(value);
601
- }
602
1055
  var sourceColors = {
603
1056
  react: "#61dafb",
604
1057
  redux: "#764abc",
@@ -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,189 +1232,446 @@ 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 {
1348
+ padding: 3rem;
1349
+ }
1350
+
1351
+ .ts-empty-state {
757
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 {
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;
1456
+ }
1457
+
1458
+ .ts-chart-track {
1459
+ position: relative;
1460
+ height: 32px;
1461
+ background: #16213e;
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;
1550
+ margin-bottom: 0.5rem;
1551
+ }
1552
+
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;
774
1577
  color: #888;
775
- font-family: monospace;
776
1578
  }
777
1579
 
778
- .timeline-scrubber__track {
779
- position: relative;
780
- height: 24px;
781
- background: #16213e;
782
- border-radius: 4px;
783
- margin-bottom: 0.5rem;
1580
+ .ts-preview-component {
1581
+ color: #a78bfa;
784
1582
  }
785
1583
 
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;
1584
+ .ts-preview-changes {
1585
+ display: flex;
1586
+ flex-wrap: wrap;
1587
+ gap: 0.5rem;
1588
+ margin-top: 0.75rem;
795
1589
  }
796
1590
 
797
- .timeline-scrubber__marker:hover {
798
- transform: scaleY(1.2);
799
- opacity: 1;
1591
+ .ts-preview-path {
1592
+ padding: 0.25rem 0.5rem;
1593
+ border-radius: 4px;
1594
+ font-size: 0.75rem;
800
1595
  }
801
1596
 
802
- .timeline-scrubber__marker--selected {
803
- opacity: 1;
804
- box-shadow: 0 0 8px currentColor;
805
- }
1597
+ .ts-preview-add { background: #22c55e20; color: #86efac; }
1598
+ .ts-preview-update { background: #fbbf2420; color: #fcd34d; }
1599
+ .ts-preview-remove { background: #ef444420; color: #fca5a5; }
806
1600
 
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;
1601
+ .ts-preview-more {
1602
+ font-size: 0.75rem;
1603
+ color: #666;
1604
+ padding: 0.25rem;
815
1605
  }
816
1606
 
817
- .timeline-scrubber__slider {
818
- width: 100%;
819
- margin: 0.5rem 0;
820
- cursor: pointer;
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;
821
1614
  }
822
1615
 
823
- .timeline-scrubber__controls {
1616
+ .ts-control-group {
824
1617
  display: flex;
825
- justify-content: center;
1618
+ align-items: center;
826
1619
  gap: 0.5rem;
827
- margin: 0.5rem 0;
828
1620
  }
829
1621
 
830
- .timeline-scrubber__button {
831
- padding: 0.5rem 1rem;
1622
+ .ts-btn {
1623
+ padding: 0.5rem 0.75rem;
832
1624
  border: none;
833
- border-radius: 4px;
1625
+ border-radius: 8px;
834
1626
  background: #16213e;
835
1627
  color: #fff;
836
1628
  cursor: pointer;
837
1629
  font-size: 1rem;
838
- transition: background 0.2s;
1630
+ transition: all 0.2s;
839
1631
  }
840
1632
 
841
- .timeline-scrubber__button:hover:not(:disabled) {
1633
+ .ts-btn:hover:not(:disabled) {
842
1634
  background: #1f2b4d;
843
1635
  }
844
1636
 
845
- .timeline-scrubber__button:disabled {
846
- opacity: 0.5;
1637
+ .ts-btn:disabled {
1638
+ opacity: 0.4;
847
1639
  cursor: not-allowed;
848
1640
  }
849
1641
 
850
- .timeline-scrubber__button--primary {
1642
+ .ts-btn-primary {
851
1643
  background: #00d9ff;
852
1644
  color: #000;
853
1645
  }
854
1646
 
855
- .timeline-scrubber__button--primary:hover:not(:disabled) {
1647
+ .ts-btn-primary:hover:not(:disabled) {
856
1648
  background: #00b8e6;
857
1649
  }
858
1650
 
859
- .timeline-scrubber__legend {
860
- display: flex;
861
- flex-wrap: wrap;
862
- gap: 1rem;
863
- margin-top: 0.5rem;
864
- font-size: 0.75rem;
1651
+ .ts-btn-text {
1652
+ background: transparent;
1653
+ color: #888;
1654
+ font-size: 0.875rem;
865
1655
  }
866
1656
 
867
- .timeline-scrubber__legend-item {
868
- display: flex;
869
- align-items: center;
870
- gap: 0.25rem;
1657
+ .ts-btn-text:hover:not(:disabled) {
1658
+ color: #fff;
1659
+ background: transparent;
1660
+ }
1661
+
1662
+ .ts-speed-label {
1663
+ font-size: 0.875rem;
871
1664
  color: #888;
872
1665
  }
873
1666
 
874
- .timeline-scrubber__legend-dot {
875
- width: 8px;
876
- height: 8px;
877
- border-radius: 50%;
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;
878
1675
  }
879
1676
  ` })
880
1677
  ] });
@@ -992,44 +1789,139 @@ function DashboardContent() {
992
1789
  loadSessions
993
1790
  } = useDashboard();
994
1791
  const playback = usePlayback(timeline);
995
- React2.useEffect(() => {
1792
+ const [activePanel, setActivePanel] = useState("diff");
1793
+ const [autoRefresh, setAutoRefresh] = useState(true);
1794
+ useEffect(() => {
996
1795
  if (playback.currentMutation) {
997
1796
  selectMutation(playback.currentMutation);
998
1797
  }
999
1798
  }, [playback.currentMutation, selectMutation]);
1799
+ useEffect(() => {
1800
+ if (!autoRefresh) return;
1801
+ const interval = setInterval(loadSessions, 5e3);
1802
+ return () => clearInterval(interval);
1803
+ }, [autoRefresh, loadSessions]);
1804
+ useEffect(() => {
1805
+ const handleKeyDown = (e) => {
1806
+ if (e.target instanceof HTMLInputElement) return;
1807
+ switch (e.key) {
1808
+ case "ArrowLeft":
1809
+ playback.stepBackward();
1810
+ break;
1811
+ case "ArrowRight":
1812
+ playback.stepForward();
1813
+ break;
1814
+ case " ":
1815
+ e.preventDefault();
1816
+ playback.isPlaying ? playback.pause() : playback.play();
1817
+ break;
1818
+ }
1819
+ };
1820
+ window.addEventListener("keydown", handleKeyDown);
1821
+ return () => window.removeEventListener("keydown", handleKeyDown);
1822
+ }, [playback]);
1000
1823
  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",
1824
+ /* @__PURE__ */ jsxs("header", { className: "sd-header", children: [
1825
+ /* @__PURE__ */ jsx("div", { className: "sd-header-left", children: /* @__PURE__ */ jsxs("h1", { className: "sd-logo", children: [
1826
+ /* @__PURE__ */ jsx("span", { className: "sd-logo-icon", children: "\u{1F52C}" }),
1827
+ /* @__PURE__ */ jsx("span", { className: "sd-logo-text", children: "State Surgeon" }),
1828
+ /* @__PURE__ */ jsx("span", { className: "sd-version", children: "v1.1.0" })
1829
+ ] }) }),
1830
+ /* @__PURE__ */ jsx("div", { className: "sd-header-center", children: /* @__PURE__ */ jsxs(
1831
+ "select",
1832
+ {
1833
+ value: currentSession?.id || "",
1834
+ onChange: (e) => selectSession(e.target.value),
1835
+ disabled: isLoading,
1836
+ className: "sd-session-select",
1837
+ children: [
1838
+ /* @__PURE__ */ jsx("option", { value: "", children: "Select a session..." }),
1839
+ sessions.map((session) => /* @__PURE__ */ jsxs("option", { value: session.id, children: [
1840
+ session.appId,
1841
+ " \u2022 ",
1842
+ session.id.slice(0, 8),
1843
+ "... \u2022 ",
1844
+ session.mutationCount,
1845
+ " mutations"
1846
+ ] }, session.id))
1847
+ ]
1848
+ }
1849
+ ) }),
1850
+ /* @__PURE__ */ jsxs("div", { className: "sd-header-right", children: [
1851
+ /* @__PURE__ */ jsxs("label", { className: "sd-auto-refresh", children: [
1852
+ /* @__PURE__ */ jsx(
1853
+ "input",
1854
+ {
1855
+ type: "checkbox",
1856
+ checked: autoRefresh,
1857
+ onChange: (e) => setAutoRefresh(e.target.checked)
1858
+ }
1859
+ ),
1860
+ "Auto-refresh"
1861
+ ] }),
1862
+ /* @__PURE__ */ jsx("button", { onClick: loadSessions, disabled: isLoading, className: "sd-refresh-btn", children: isLoading ? "\u23F3" : "\u{1F504}" })
1863
+ ] })
1864
+ ] }),
1865
+ error && /* @__PURE__ */ jsxs("div", { className: "sd-error", children: [
1866
+ /* @__PURE__ */ jsx("span", { className: "sd-error-icon", children: "\u274C" }),
1867
+ /* @__PURE__ */ jsx("p", { children: error })
1868
+ ] }),
1869
+ !currentSession && !isLoading && /* @__PURE__ */ jsx("div", { className: "sd-welcome", children: /* @__PURE__ */ jsxs("div", { className: "sd-welcome-content", children: [
1870
+ /* @__PURE__ */ jsx("div", { className: "sd-welcome-icon", children: "\u{1F52C}" }),
1871
+ /* @__PURE__ */ jsx("h2", { children: "Welcome to State Surgeon" }),
1872
+ /* @__PURE__ */ jsx("p", { children: "A forensic debugging platform for JavaScript applications" }),
1873
+ sessions.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "sd-welcome-steps", children: [
1874
+ /* @__PURE__ */ jsx("h3", { children: "Getting Started" }),
1875
+ /* @__PURE__ */ jsxs("ol", { children: [
1876
+ /* @__PURE__ */ jsxs("li", { children: [
1877
+ /* @__PURE__ */ jsx("strong", { children: "Install the client in your app:" }),
1878
+ /* @__PURE__ */ jsx("pre", { children: `import { StateSurgeonClient, instrumentReact } from 'state-surgeon/instrument';
1879
+
1880
+ const client = new StateSurgeonClient({ appId: 'my-app' });
1881
+ instrumentReact(React);` })
1882
+ ] }),
1883
+ /* @__PURE__ */ jsxs("li", { children: [
1884
+ /* @__PURE__ */ jsx("strong", { children: "Interact with your application" }),
1885
+ " to generate state mutations"
1886
+ ] }),
1887
+ /* @__PURE__ */ jsxs("li", { children: [
1888
+ /* @__PURE__ */ jsx("strong", { children: "Select a session above" }),
1889
+ " to start debugging"
1890
+ ] })
1891
+ ] })
1892
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "sd-welcome-sessions", children: [
1893
+ /* @__PURE__ */ jsxs("h3", { children: [
1894
+ "Available Sessions (",
1895
+ sessions.length,
1896
+ ")"
1897
+ ] }),
1898
+ /* @__PURE__ */ jsx("div", { className: "sd-session-list", children: sessions.map((session) => /* @__PURE__ */ jsxs(
1899
+ "button",
1006
1900
  {
1007
- value: currentSession?.id || "",
1008
- onChange: (e) => selectSession(e.target.value),
1009
- disabled: isLoading,
1901
+ className: "sd-session-card",
1902
+ onClick: () => selectSession(session.id),
1010
1903
  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
- " - ",
1904
+ /* @__PURE__ */ jsx("span", { className: "sd-session-app", children: session.appId }),
1905
+ /* @__PURE__ */ jsxs("span", { className: "sd-session-id", children: [
1015
1906
  session.id.slice(0, 16),
1016
- "... (",
1907
+ "..."
1908
+ ] }),
1909
+ /* @__PURE__ */ jsxs("span", { className: "sd-session-count", children: [
1017
1910
  session.mutationCount,
1018
- " mutations)"
1019
- ] }, session.id))
1911
+ " mutations"
1912
+ ] })
1020
1913
  ]
1021
- }
1022
- ),
1023
- /* @__PURE__ */ jsx("button", { onClick: loadSessions, disabled: isLoading, children: "\u{1F504}" })
1914
+ },
1915
+ session.id
1916
+ )) })
1024
1917
  ] })
1025
- ] }),
1026
- error && /* @__PURE__ */ jsx("div", { className: "surgeon-dashboard__error", children: /* @__PURE__ */ jsxs("p", { children: [
1027
- "Error: ",
1028
- error
1029
1918
  ] }) }),
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(
1919
+ isLoading && !currentSession && /* @__PURE__ */ jsxs("div", { className: "sd-loading", children: [
1920
+ /* @__PURE__ */ jsx("div", { className: "sd-spinner" }),
1921
+ /* @__PURE__ */ jsx("p", { children: "Loading sessions..." })
1922
+ ] }),
1923
+ currentSession && /* @__PURE__ */ jsxs("div", { className: "sd-main", children: [
1924
+ /* @__PURE__ */ jsx("section", { className: "sd-timeline", children: /* @__PURE__ */ jsx(
1033
1925
  TimelineScrubber,
1034
1926
  {
1035
1927
  mutations: timeline,
@@ -1042,135 +1934,373 @@ function DashboardContent() {
1042
1934
  onPlay: playback.play,
1043
1935
  onPause: playback.pause,
1044
1936
  onStepForward: playback.stepForward,
1045
- onStepBackward: playback.stepBackward
1937
+ onStepBackward: playback.stepBackward,
1938
+ playbackSpeed: playback.playbackSpeed,
1939
+ onSpeedChange: playback.setSpeed
1046
1940
  }
1047
1941
  ) }),
1048
- /* @__PURE__ */ jsxs("div", { className: "surgeon-dashboard__content", children: [
1049
- /* @__PURE__ */ jsx("section", { className: "surgeon-dashboard__diff", children: selectedMutation && /* @__PURE__ */ jsx(
1050
- StateDiffViewer,
1051
- {
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 }) })
1942
+ /* @__PURE__ */ jsxs("div", { className: "sd-panels", children: [
1943
+ /* @__PURE__ */ jsxs("div", { className: "sd-panel-tabs", children: [
1944
+ /* @__PURE__ */ jsx(
1945
+ "button",
1946
+ {
1947
+ className: `sd-panel-tab ${activePanel === "diff" ? "active" : ""}`,
1948
+ onClick: () => setActivePanel("diff"),
1949
+ children: "\u{1F4CA} State Changes"
1950
+ }
1951
+ ),
1952
+ /* @__PURE__ */ jsx(
1953
+ "button",
1954
+ {
1955
+ className: `sd-panel-tab ${activePanel === "inspector" ? "active" : ""}`,
1956
+ onClick: () => setActivePanel("inspector"),
1957
+ children: "\u{1F50D} Mutation Details"
1958
+ }
1959
+ )
1960
+ ] }),
1961
+ /* @__PURE__ */ jsxs("div", { className: "sd-panel-content", children: [
1962
+ activePanel === "diff" && selectedMutation && /* @__PURE__ */ jsx(
1963
+ StateDiffViewer,
1964
+ {
1965
+ previousState: selectedMutation.previousState,
1966
+ nextState: selectedMutation.nextState,
1967
+ diff: selectedMutation.diff
1968
+ }
1969
+ ),
1970
+ activePanel === "inspector" && /* @__PURE__ */ jsx(MutationInspector, { mutation: selectedMutation }),
1971
+ !selectedMutation && /* @__PURE__ */ jsx("div", { className: "sd-panel-empty", children: /* @__PURE__ */ jsx("p", { children: "Select a mutation from the timeline to view details" }) })
1972
+ ] })
1058
1973
  ] })
1059
1974
  ] }),
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);` })
1975
+ /* @__PURE__ */ jsxs("footer", { className: "sd-footer", children: [
1976
+ /* @__PURE__ */ jsx("span", { children: "State Surgeon v1.1.0" }),
1977
+ /* @__PURE__ */ jsx("span", { className: "sd-footer-sep", children: "\u2022" }),
1978
+ /* @__PURE__ */ jsx("span", { children: "Press \u2190 \u2192 to navigate, Space to play/pause" })
1073
1979
  ] }),
1074
1980
  /* @__PURE__ */ jsx("style", { children: `
1075
1981
  .surgeon-dashboard {
1076
1982
  min-height: 100vh;
1077
- background: #0f0f23;
1983
+ display: flex;
1984
+ flex-direction: column;
1985
+ background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 100%);
1078
1986
  color: #e0e0e0;
1079
1987
  font-family: system-ui, -apple-system, sans-serif;
1080
1988
  }
1081
1989
 
1082
- .surgeon-dashboard__header {
1990
+ .sd-header {
1083
1991
  display: flex;
1084
1992
  justify-content: space-between;
1085
1993
  align-items: center;
1086
1994
  padding: 1rem 2rem;
1087
- background: #1a1a2e;
1995
+ background: rgba(22, 33, 62, 0.8);
1996
+ backdrop-filter: blur(10px);
1088
1997
  border-bottom: 1px solid #333;
1998
+ position: sticky;
1999
+ top: 0;
2000
+ z-index: 100;
1089
2001
  }
1090
2002
 
1091
- .surgeon-dashboard__logo {
1092
- margin: 0;
1093
- font-size: 1.5rem;
1094
- color: #00d9ff;
2003
+ .sd-header-left, .sd-header-right {
2004
+ display: flex;
2005
+ align-items: center;
2006
+ gap: 1rem;
1095
2007
  }
1096
2008
 
1097
- .surgeon-dashboard__session-select {
2009
+ .sd-logo {
1098
2010
  display: flex;
2011
+ align-items: center;
1099
2012
  gap: 0.5rem;
2013
+ margin: 0;
2014
+ font-size: 1.25rem;
1100
2015
  }
1101
2016
 
1102
- .surgeon-dashboard__session-select select {
1103
- padding: 0.5rem 1rem;
1104
- border: 1px solid #333;
2017
+ .sd-logo-icon {
2018
+ font-size: 1.5rem;
2019
+ }
2020
+
2021
+ .sd-logo-text {
2022
+ background: linear-gradient(135deg, #00d9ff, #a78bfa);
2023
+ -webkit-background-clip: text;
2024
+ -webkit-text-fill-color: transparent;
2025
+ background-clip: text;
2026
+ font-weight: 700;
2027
+ }
2028
+
2029
+ .sd-version {
2030
+ font-size: 0.7rem;
2031
+ padding: 0.2rem 0.4rem;
2032
+ background: #333;
1105
2033
  border-radius: 4px;
1106
- background: #16213e;
2034
+ color: #888;
2035
+ }
2036
+
2037
+ .sd-session-select {
2038
+ padding: 0.625rem 1.25rem;
2039
+ min-width: 350px;
2040
+ border: 1px solid #333;
2041
+ border-radius: 8px;
2042
+ background: #0f0f23;
1107
2043
  color: #e0e0e0;
1108
- min-width: 300px;
2044
+ font-size: 0.875rem;
2045
+ cursor: pointer;
1109
2046
  }
1110
2047
 
1111
- .surgeon-dashboard__session-select button {
1112
- padding: 0.5rem 1rem;
2048
+ .sd-session-select:focus {
2049
+ outline: none;
2050
+ border-color: #00d9ff;
2051
+ }
2052
+
2053
+ .sd-auto-refresh {
2054
+ display: flex;
2055
+ align-items: center;
2056
+ gap: 0.5rem;
2057
+ font-size: 0.875rem;
2058
+ color: #888;
2059
+ cursor: pointer;
2060
+ }
2061
+
2062
+ .sd-refresh-btn {
2063
+ padding: 0.5rem;
1113
2064
  border: 1px solid #333;
1114
- border-radius: 4px;
1115
- background: #16213e;
2065
+ border-radius: 8px;
2066
+ background: transparent;
1116
2067
  color: #e0e0e0;
1117
2068
  cursor: pointer;
2069
+ font-size: 1rem;
1118
2070
  }
1119
2071
 
1120
- .surgeon-dashboard__session-select button:hover {
1121
- background: #1f2b4d;
2072
+ .sd-refresh-btn:hover {
2073
+ background: #16213e;
1122
2074
  }
1123
2075
 
1124
- .surgeon-dashboard__error {
2076
+ .sd-error {
2077
+ display: flex;
2078
+ align-items: center;
2079
+ gap: 0.75rem;
1125
2080
  padding: 1rem 2rem;
1126
2081
  background: rgba(239, 68, 68, 0.1);
1127
2082
  border-bottom: 1px solid #ef4444;
2083
+ }
2084
+
2085
+ .sd-error-icon {
2086
+ font-size: 1.25rem;
2087
+ }
2088
+
2089
+ .sd-error p {
2090
+ margin: 0;
1128
2091
  color: #fca5a5;
1129
2092
  }
1130
2093
 
1131
- .surgeon-dashboard__loading {
2094
+ .sd-welcome {
2095
+ flex: 1;
2096
+ display: flex;
2097
+ align-items: center;
2098
+ justify-content: center;
1132
2099
  padding: 2rem;
1133
- text-align: center;
1134
- color: #888;
1135
2100
  }
1136
2101
 
1137
- .surgeon-dashboard__main {
1138
- padding: 1rem 2rem;
2102
+ .sd-welcome-content {
2103
+ text-align: center;
2104
+ max-width: 600px;
1139
2105
  }
1140
2106
 
1141
- .surgeon-dashboard__timeline {
2107
+ .sd-welcome-icon {
2108
+ font-size: 5rem;
1142
2109
  margin-bottom: 1rem;
2110
+ animation: float 3s ease-in-out infinite;
1143
2111
  }
1144
2112
 
1145
- .surgeon-dashboard__content {
1146
- display: grid;
1147
- grid-template-columns: 2fr 1fr;
1148
- gap: 1rem;
2113
+ @keyframes float {
2114
+ 0%, 100% { transform: translateY(0); }
2115
+ 50% { transform: translateY(-10px); }
1149
2116
  }
1150
2117
 
1151
- .surgeon-dashboard__empty {
1152
- padding: 4rem 2rem;
1153
- text-align: center;
2118
+ .sd-welcome h2 {
2119
+ margin: 0 0 0.5rem;
2120
+ font-size: 2rem;
2121
+ background: linear-gradient(135deg, #00d9ff, #a78bfa);
2122
+ -webkit-background-clip: text;
2123
+ -webkit-text-fill-color: transparent;
2124
+ background-clip: text;
1154
2125
  }
1155
2126
 
1156
- .surgeon-dashboard__empty h2 {
2127
+ .sd-welcome > p {
2128
+ margin: 0 0 2rem;
1157
2129
  color: #888;
1158
- margin-bottom: 1rem;
1159
2130
  }
1160
2131
 
1161
- .surgeon-dashboard__empty pre {
1162
- display: inline-block;
2132
+ .sd-welcome-steps {
1163
2133
  text-align: left;
2134
+ background: #16213e;
2135
+ border-radius: 12px;
1164
2136
  padding: 1.5rem;
2137
+ }
2138
+
2139
+ .sd-welcome-steps h3 {
2140
+ margin: 0 0 1rem;
2141
+ color: #00d9ff;
2142
+ font-size: 1rem;
2143
+ }
2144
+
2145
+ .sd-welcome-steps ol {
2146
+ margin: 0;
2147
+ padding-left: 1.25rem;
2148
+ }
2149
+
2150
+ .sd-welcome-steps li {
2151
+ margin-bottom: 1rem;
2152
+ color: #ccc;
2153
+ }
2154
+
2155
+ .sd-welcome-steps pre {
2156
+ margin: 0.5rem 0 0;
2157
+ padding: 1rem;
2158
+ background: #0f0f23;
2159
+ border-radius: 8px;
2160
+ font-size: 0.8rem;
2161
+ overflow-x: auto;
2162
+ }
2163
+
2164
+ .sd-welcome-sessions h3 {
2165
+ margin: 0 0 1rem;
2166
+ color: #00d9ff;
2167
+ }
2168
+
2169
+ .sd-session-list {
2170
+ display: grid;
2171
+ gap: 0.75rem;
2172
+ }
2173
+
2174
+ .sd-session-card {
2175
+ display: flex;
2176
+ justify-content: space-between;
2177
+ align-items: center;
2178
+ padding: 1rem 1.25rem;
1165
2179
  background: #16213e;
2180
+ border: 1px solid #333;
1166
2181
  border-radius: 8px;
2182
+ cursor: pointer;
2183
+ transition: all 0.2s;
2184
+ text-align: left;
2185
+ color: inherit;
2186
+ }
2187
+
2188
+ .sd-session-card:hover {
2189
+ border-color: #00d9ff;
2190
+ background: #1a2744;
2191
+ }
2192
+
2193
+ .sd-session-app {
2194
+ font-weight: 600;
2195
+ color: #a78bfa;
2196
+ }
2197
+
2198
+ .sd-session-id {
2199
+ font-family: monospace;
2200
+ font-size: 0.8rem;
2201
+ color: #666;
2202
+ }
2203
+
2204
+ .sd-session-count {
1167
2205
  font-size: 0.875rem;
2206
+ color: #00d9ff;
1168
2207
  }
1169
2208
 
1170
- @media (max-width: 1024px) {
1171
- .surgeon-dashboard__content {
1172
- grid-template-columns: 1fr;
1173
- }
2209
+ .sd-loading {
2210
+ flex: 1;
2211
+ display: flex;
2212
+ flex-direction: column;
2213
+ align-items: center;
2214
+ justify-content: center;
2215
+ gap: 1rem;
2216
+ }
2217
+
2218
+ .sd-spinner {
2219
+ width: 40px;
2220
+ height: 40px;
2221
+ border: 3px solid #333;
2222
+ border-top-color: #00d9ff;
2223
+ border-radius: 50%;
2224
+ animation: spin 1s linear infinite;
2225
+ }
2226
+
2227
+ @keyframes spin {
2228
+ to { transform: rotate(360deg); }
2229
+ }
2230
+
2231
+ .sd-main {
2232
+ flex: 1;
2233
+ padding: 1.5rem 2rem;
2234
+ display: flex;
2235
+ flex-direction: column;
2236
+ gap: 1.5rem;
2237
+ }
2238
+
2239
+ .sd-timeline {
2240
+ flex-shrink: 0;
2241
+ }
2242
+
2243
+ .sd-panels {
2244
+ flex: 1;
2245
+ background: #1a1a2e;
2246
+ border-radius: 12px;
2247
+ overflow: hidden;
2248
+ display: flex;
2249
+ flex-direction: column;
2250
+ min-height: 400px;
2251
+ }
2252
+
2253
+ .sd-panel-tabs {
2254
+ display: flex;
2255
+ background: #16213e;
2256
+ border-bottom: 1px solid #333;
2257
+ }
2258
+
2259
+ .sd-panel-tab {
2260
+ padding: 1rem 1.5rem;
2261
+ border: none;
2262
+ background: transparent;
2263
+ color: #888;
2264
+ cursor: pointer;
2265
+ font-size: 0.875rem;
2266
+ transition: all 0.2s;
2267
+ }
2268
+
2269
+ .sd-panel-tab:hover {
2270
+ color: #ccc;
2271
+ background: #1a2744;
2272
+ }
2273
+
2274
+ .sd-panel-tab.active {
2275
+ color: #00d9ff;
2276
+ background: #1a1a2e;
2277
+ border-bottom: 2px solid #00d9ff;
2278
+ }
2279
+
2280
+ .sd-panel-content {
2281
+ flex: 1;
2282
+ overflow: auto;
2283
+ }
2284
+
2285
+ .sd-panel-empty {
2286
+ height: 100%;
2287
+ display: flex;
2288
+ align-items: center;
2289
+ justify-content: center;
2290
+ color: #666;
2291
+ }
2292
+
2293
+ .sd-footer {
2294
+ padding: 0.75rem 2rem;
2295
+ background: rgba(22, 33, 62, 0.5);
2296
+ border-top: 1px solid #333;
2297
+ font-size: 0.75rem;
2298
+ color: #666;
2299
+ text-align: center;
2300
+ }
2301
+
2302
+ .sd-footer-sep {
2303
+ margin: 0 0.5rem;
1174
2304
  }
1175
2305
  ` })
1176
2306
  ] });