nuxt-devtools-observatory 0.1.28 → 0.1.31

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.
Files changed (48) hide show
  1. package/README.md +93 -11
  2. package/client/.env +2 -1
  3. package/client/.env.example +2 -1
  4. package/client/dist/assets/index-BuMXDBO9.js +17 -0
  5. package/client/dist/assets/index-CwcspZ6w.css +1 -0
  6. package/client/dist/index.html +2 -2
  7. package/client/src/App.vue +4 -0
  8. package/client/src/components/Flamegraph.vue +443 -0
  9. package/client/src/components/SpanInspector.vue +446 -0
  10. package/client/src/components/TraceFilter.vue +344 -0
  11. package/client/src/components/WaterfallView.vue +443 -0
  12. package/client/src/composables/useResizablePane.ts +65 -0
  13. package/client/src/composables/useTraceFilter.ts +164 -0
  14. package/client/src/stores/observatory.ts +16 -2
  15. package/client/src/style.css +203 -28
  16. package/client/src/views/ComposableTracker.vue +324 -259
  17. package/client/src/views/FetchDashboard.vue +104 -133
  18. package/client/src/views/ProvideInjectGraph.vue +99 -109
  19. package/client/src/views/RenderHeatmap.vue +191 -147
  20. package/client/src/views/TraceViewer.vue +599 -0
  21. package/client/src/views/TransitionTimeline.vue +167 -137
  22. package/client/tsconfig.json +3 -1
  23. package/client/vite.config.ts +8 -0
  24. package/dist/module.d.mts +5 -0
  25. package/dist/module.json +1 -1
  26. package/dist/module.mjs +186 -200
  27. package/dist/runtime/composables/render-registry.js +66 -110
  28. package/dist/runtime/composables/transition-registry.js +103 -28
  29. package/dist/runtime/instrumentation/asyncData.d.ts +9 -0
  30. package/dist/runtime/instrumentation/asyncData.js +49 -0
  31. package/dist/runtime/instrumentation/component.d.ts +2 -0
  32. package/dist/runtime/instrumentation/component.js +126 -0
  33. package/dist/runtime/instrumentation/fetch.d.ts +2 -0
  34. package/dist/runtime/instrumentation/fetch.js +89 -0
  35. package/dist/runtime/instrumentation/route.d.ts +6 -0
  36. package/dist/runtime/instrumentation/route.js +41 -0
  37. package/dist/runtime/plugin.js +39 -3
  38. package/dist/runtime/tracing/context.d.ts +9 -0
  39. package/dist/runtime/tracing/context.js +15 -0
  40. package/dist/runtime/tracing/trace.d.ts +25 -0
  41. package/dist/runtime/tracing/trace.js +0 -0
  42. package/dist/runtime/tracing/traceStore.d.ts +39 -0
  43. package/dist/runtime/tracing/traceStore.js +101 -0
  44. package/dist/runtime/tracing/tracing.d.ts +27 -0
  45. package/dist/runtime/tracing/tracing.js +48 -0
  46. package/package.json +9 -6
  47. package/client/dist/assets/index-DXCGQOSF.js +0 -17
  48. package/client/dist/assets/index-htI4WwBU.css +0 -1
@@ -1,7 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, computed, watch } from 'vue'
3
- import { useObservatoryData, openInEditor as openInEditorFromStore } from '../stores/observatory'
4
- import type { InjectEntry, ProvideEntry } from '../../../src/types/snapshot'
3
+ import { useResizablePane } from '@observatory-client/composables/useResizablePane'
4
+ import { useObservatoryData, openInEditor as openInEditorFromStore } from '@observatory-client/stores/observatory'
5
+ import type { InjectEntry, ProvideEntry } from '@observatory/types/snapshot'
5
6
 
6
7
  interface TreeNodeData {
7
8
  id: string
@@ -46,6 +47,7 @@ const V_GAP = 72
46
47
  const H_GAP = 18
47
48
 
48
49
  const { provideInject, connected } = useObservatoryData()
50
+ const { paneWidth: detailWidth, onHandleMouseDown } = useResizablePane(280, 'observatory:provide:detailWidth')
49
51
 
50
52
  function nodeColor(node: TreeNodeData): string {
51
53
  if (node.injects.some((entry) => !entry.ok)) {
@@ -450,20 +452,20 @@ const edges = computed<Edge[]>(() => {
450
452
  </script>
451
453
 
452
454
  <template>
453
- <div class="view">
454
- <div class="toolbar">
455
+ <div class="provide-graph tracker-view">
456
+ <div class="provide-graph__toolbar tracker-toolbar">
455
457
  <button :class="{ active: activeFilter === 'all' }" @click="activeFilter = 'all'">all keys</button>
456
458
  <button
457
459
  v-for="key in allKeys"
458
460
  :key="key"
459
- style="font-family: var(--mono)"
461
+ class="provide-graph__key-filter mono"
460
462
  :class="{ active: activeFilter === key }"
461
463
  @click="activeFilter = key"
462
464
  >
463
465
  {{ key }}
464
466
  </button>
465
467
  <button
466
- style="margin-left: auto"
468
+ class="provide-graph__toolbar-spacer"
467
469
  :class="{ 'danger-active': activeFilter === 'shadow' }"
468
470
  @click="activeFilter = activeFilter === 'shadow' ? 'all' : 'shadow'"
469
471
  >
@@ -472,37 +474,37 @@ const edges = computed<Edge[]>(() => {
472
474
  <button :class="{ 'danger-active': activeFilter === 'warn' }" @click="activeFilter = activeFilter === 'warn' ? 'all' : 'warn'">
473
475
  warnings
474
476
  </button>
475
- <input v-model="searchQuery" type="search" placeholder="search component or key…" style="max-width: 200px" />
477
+ <input v-model="searchQuery" type="search" class="provide-graph__search" placeholder="search component or key…" />
476
478
  </div>
477
479
 
478
- <div class="split">
479
- <div class="graph-area">
480
- <div class="legend">
481
- <span class="dot" style="background: var(--teal)"></span>
480
+ <div class="provide-graph__split tracker-split">
481
+ <div class="provide-graph__graph-area">
482
+ <div class="provide-graph__legend">
483
+ <span class="provide-graph__legend-dot provide-graph__legend-dot--provides"></span>
482
484
  <span>provides</span>
483
- <span class="dot" style="background: var(--blue)"></span>
485
+ <span class="provide-graph__legend-dot provide-graph__legend-dot--both"></span>
484
486
  <span>both</span>
485
- <span class="dot" style="background: var(--text3)"></span>
487
+ <span class="provide-graph__legend-dot provide-graph__legend-dot--injects"></span>
486
488
  <span>injects</span>
487
- <span class="dot" style="background: var(--red)"></span>
489
+ <span class="provide-graph__legend-dot provide-graph__legend-dot--missing"></span>
488
490
  <span>missing provider</span>
489
491
  </div>
490
- <div v-if="layout.length" class="canvas-stage">
491
- <div class="canvas-wrap" :style="{ width: `${canvasW}px`, height: `${canvasH}px` }">
492
- <svg class="edges-svg" :width="canvasW" :height="canvasH" :viewBox="`0 0 ${canvasW} ${canvasH}`">
492
+ <div v-if="layout.length" class="provide-graph__canvas-stage">
493
+ <div class="provide-graph__canvas-wrap" :style="{ width: `${canvasW}px`, height: `${canvasH}px` }">
494
+ <svg class="provide-graph__edges-svg" :width="canvasW" :height="canvasH" :viewBox="`0 0 ${canvasW} ${canvasH}`">
493
495
  <path
494
496
  v-for="edge in edges"
495
497
  :key="edge.id"
496
498
  :d="`M ${edge.x1},${edge.y1} C ${edge.x1},${(edge.y1 + edge.y2) / 2} ${edge.x2},${(edge.y1 + edge.y2) / 2} ${edge.x2},${edge.y2}`"
497
- class="edge"
499
+ class="provide-graph__edge"
498
500
  fill="none"
499
501
  />
500
502
  </svg>
501
503
  <div
502
504
  v-for="layoutNode in layout"
503
505
  :key="layoutNode.data.id"
504
- class="graph-node"
505
- :class="{ 'is-selected': selectedNode?.id === layoutNode.data.id }"
506
+ class="provide-graph__node"
507
+ :class="{ 'provide-graph__node--selected': selectedNode?.id === layoutNode.data.id }"
506
508
  :style="{
507
509
  left: `${layoutNode.x - NODE_W / 2}px`,
508
510
  top: `${layoutNode.y - NODE_H / 2}px`,
@@ -511,8 +513,8 @@ const edges = computed<Edge[]>(() => {
511
513
  }"
512
514
  @click="selectedNode = layoutNode.data"
513
515
  >
514
- <span class="node-dot" :style="{ background: nodeColor(layoutNode.data) }"></span>
515
- <span class="mono node-label">{{ layoutNode.data.label }}</span>
516
+ <span class="provide-graph__node-dot" :style="{ background: nodeColor(layoutNode.data) }"></span>
517
+ <span class="mono provide-graph__node-label">{{ layoutNode.data.label }}</span>
516
518
  <span v-if="layoutNode.data.provides.length" class="badge badge-ok badge-xs">
517
519
  +{{ layoutNode.data.provides.length }}
518
520
  </span>
@@ -520,14 +522,16 @@ const edges = computed<Edge[]>(() => {
520
522
  </div>
521
523
  </div>
522
524
  </div>
523
- <div v-else class="graph-empty">
525
+ <div v-else class="provide-graph__graph-empty">
524
526
  {{ connected ? 'No components match the current provide/inject filter.' : 'Waiting for connection to the Nuxt app…' }}
525
527
  </div>
526
528
  </div>
527
529
 
528
- <div v-if="selectedNode" class="detail-panel">
529
- <div class="detail-header">
530
- <span class="mono bold" style="font-size: 12px">{{ selectedNode.label }}</span>
530
+ <div v-if="selectedNode" class="tracker-resize-handle" @mousedown="onHandleMouseDown" />
531
+
532
+ <div v-if="selectedNode" class="provide-graph__detail tracker-detail-panel" :style="{ width: detailWidth + 'px' }">
533
+ <div class="provide-graph__detail-header">
534
+ <span class="provide-graph__detail-title mono bold">{{ selectedNode.label }}</span>
531
535
  <button
532
536
  v-if="selectedNode.componentFile && selectedNode.componentFile !== 'unknown'"
533
537
  class="jump-btn"
@@ -540,7 +544,7 @@ const edges = computed<Edge[]>(() => {
540
544
  </div>
541
545
 
542
546
  <div v-if="selectedNode.provides.length" class="detail-section">
543
- <div class="section-label">provides ({{ selectedNode.provides.length }})</div>
547
+ <div class="tracker-section-label provide-graph__section-label">provides ({{ selectedNode.provides.length }})</div>
544
548
  <div class="detail-list">
545
549
  <div
546
550
  v-for="(entry, index) in selectedNode.provides"
@@ -570,7 +574,7 @@ const edges = computed<Edge[]>(() => {
570
574
  <span v-for="name in entry.consumerNames" :key="name" class="consumer-chip mono">{{ name }}</span>
571
575
  <span v-if="!entry.consumerNames.length" class="muted text-sm">no consumers</span>
572
576
  </div>
573
- <div v-else class="muted text-sm" style="padding: 2px 0; font-size: 11px">no consumers detected</div>
577
+ <div v-else class="provide-graph__compact-muted muted text-sm">no consumers detected</div>
574
578
  <pre
575
579
  v-if="entry.complex && expandedProvideValues.has(provideValueId(selectedNode.id, entry.key, index))"
576
580
  class="value-box"
@@ -585,7 +589,7 @@ const edges = computed<Edge[]>(() => {
585
589
  class="detail-section"
586
590
  :style="{ marginTop: selectedNode.provides.length ? '10px' : '0' }"
587
591
  >
588
- <div class="section-label">injects ({{ selectedNode.injects.length }})</div>
592
+ <div class="tracker-section-label provide-graph__section-label">injects ({{ selectedNode.injects.length }})</div>
589
593
  <div class="detail-list">
590
594
  <div
591
595
  v-for="entry in selectedNode.injects"
@@ -603,11 +607,11 @@ const edges = computed<Edge[]>(() => {
603
607
  </div>
604
608
  </div>
605
609
 
606
- <div v-if="!selectedNode.provides.length && !selectedNode.injects.length" class="muted text-sm" style="margin-top: 8px">
610
+ <div v-if="!selectedNode.provides.length && !selectedNode.injects.length" class="provide-graph__empty-detail muted text-sm">
607
611
  no provide/inject in this component
608
612
  </div>
609
613
  </div>
610
- <div v-else class="detail-empty">
614
+ <div v-else class="tracker-detail-empty">
611
615
  {{ connected ? 'Click a node to inspect.' : 'Waiting for connection to the Nuxt app…' }}
612
616
  </div>
613
617
  </div>
@@ -615,57 +619,40 @@ const edges = computed<Edge[]>(() => {
615
619
  </template>
616
620
 
617
621
  <style scoped>
618
- .view {
619
- display: flex;
620
- flex-direction: column;
621
- height: 100%;
622
- overflow: hidden;
623
- padding: 12px;
624
- gap: 10px;
625
- }
626
-
627
- .toolbar {
628
- display: flex;
629
- align-items: center;
630
- gap: 6px;
631
- flex-shrink: 0;
632
- flex-wrap: wrap;
622
+ .provide-graph__toolbar-spacer {
623
+ margin-left: auto;
633
624
  }
634
625
 
635
- .split {
636
- display: flex;
637
- gap: 12px;
638
- flex: 1;
639
- overflow: hidden;
640
- min-height: 0;
626
+ .provide-graph__search {
627
+ max-width: 200px;
641
628
  }
642
629
 
643
- .graph-area {
630
+ .provide-graph__graph-area {
644
631
  flex: 1;
645
632
  overflow: auto;
646
- border: 0.5px solid var(--border);
633
+ border: var(--tracker-border-width) solid var(--border);
647
634
  border-radius: var(--radius-lg);
648
- padding: 12px;
635
+ padding: var(--tracker-space-3);
649
636
  background: var(--bg3);
650
637
  }
651
638
 
652
- .legend {
639
+ .provide-graph__legend {
653
640
  display: flex;
654
641
  align-items: center;
655
- gap: 12px;
656
- font-size: 11px;
642
+ gap: var(--tracker-space-3);
643
+ font-size: var(--tracker-font-size-sm);
657
644
  color: var(--text2);
658
- margin-bottom: 12px;
645
+ margin-bottom: var(--tracker-space-3);
659
646
  }
660
647
 
661
- .canvas-stage {
648
+ .provide-graph__canvas-stage {
662
649
  display: flex;
663
650
  justify-content: center;
664
651
  align-items: flex-start;
665
652
  min-width: 100%;
666
653
  }
667
654
 
668
- .dot {
655
+ .provide-graph__legend-dot {
669
656
  width: 8px;
670
657
  height: 8px;
671
658
  border-radius: 50%;
@@ -673,23 +660,39 @@ const edges = computed<Edge[]>(() => {
673
660
  margin-right: 2px;
674
661
  }
675
662
 
676
- .canvas-wrap {
663
+ .provide-graph__legend-dot--provides {
664
+ background: var(--teal);
665
+ }
666
+
667
+ .provide-graph__legend-dot--both {
668
+ background: var(--blue);
669
+ }
670
+
671
+ .provide-graph__legend-dot--injects {
672
+ background: var(--text3);
673
+ }
674
+
675
+ .provide-graph__legend-dot--missing {
676
+ background: var(--red);
677
+ }
678
+
679
+ .provide-graph__canvas-wrap {
677
680
  position: relative;
678
681
  }
679
682
 
680
- .edges-svg {
683
+ .provide-graph__edges-svg {
681
684
  position: absolute;
682
685
  top: 0;
683
686
  left: 0;
684
687
  pointer-events: none;
685
688
  }
686
689
 
687
- .edge {
690
+ .provide-graph__edge {
688
691
  stroke: var(--border);
689
692
  stroke-width: 1.5;
690
693
  }
691
694
 
692
- .graph-node {
695
+ .provide-graph__node {
693
696
  position: absolute;
694
697
  display: flex;
695
698
  align-items: center;
@@ -697,35 +700,35 @@ const edges = computed<Edge[]>(() => {
697
700
  padding: 0 10px;
698
701
  height: 32px;
699
702
  border-radius: var(--radius);
700
- border: 0.5px solid var(--border);
703
+ border: var(--tracker-border-width) solid var(--border);
701
704
  background: var(--bg3);
702
705
  cursor: pointer;
703
706
  transition:
704
- border-color 0.12s,
705
- background 0.12s;
707
+ border-color var(--tracker-transition-fast),
708
+ background var(--tracker-transition-fast);
706
709
  overflow: hidden;
707
710
  box-sizing: border-box;
708
711
  white-space: nowrap;
709
712
  }
710
713
 
711
- .graph-node:hover {
714
+ .provide-graph__node:hover {
712
715
  border-color: var(--text3);
713
716
  }
714
717
 
715
- .graph-node.is-selected {
718
+ .provide-graph__node--selected {
716
719
  border-color: var(--node-color);
717
720
  background: color-mix(in srgb, var(--node-color) 8%, transparent);
718
721
  }
719
722
 
720
- .node-dot {
723
+ .provide-graph__node-dot {
721
724
  width: 7px;
722
725
  height: 7px;
723
726
  border-radius: 50%;
724
727
  flex-shrink: 0;
725
728
  }
726
729
 
727
- .node-label {
728
- font-size: 11px;
730
+ .provide-graph__node-label {
731
+ font-size: var(--tracker-font-size-sm);
729
732
  flex: 1;
730
733
  overflow: hidden;
731
734
  text-overflow: ellipsis;
@@ -736,54 +739,32 @@ const edges = computed<Edge[]>(() => {
736
739
  padding: 1px 4px;
737
740
  }
738
741
 
739
- .detail-panel {
740
- width: 280px;
741
- flex-shrink: 0;
742
- overflow: auto;
743
- border: 0.5px solid var(--border);
744
- border-radius: var(--radius-lg);
745
- padding: 12px;
746
- background: var(--bg3);
747
- display: flex;
748
- flex-direction: column;
749
- gap: 4px;
742
+ .provide-graph__detail {
750
743
  min-height: 0;
744
+ gap: var(--tracker-space-1);
751
745
  }
752
746
 
753
- .detail-empty {
754
- width: 280px;
755
- display: flex;
756
- align-items: center;
757
- justify-content: center;
758
- color: var(--text3);
759
- font-size: 12px;
760
- border: 0.5px dashed var(--border);
761
- border-radius: var(--radius-lg);
762
- flex-shrink: 0;
763
- }
764
-
765
- .graph-empty {
747
+ .provide-graph__graph-empty {
766
748
  display: flex;
767
749
  align-items: center;
768
750
  justify-content: center;
769
751
  min-height: 180px;
770
752
  color: var(--text3);
771
- font-size: 12px;
753
+ font-size: var(--tracker-font-size-md);
772
754
  }
773
755
 
774
- .detail-header {
756
+ .provide-graph__detail-header {
775
757
  display: flex;
776
758
  align-items: center;
777
759
  justify-content: space-between;
778
- margin-bottom: 6px;
760
+ margin-bottom: var(--tracker-gap-toolbar);
779
761
  }
780
762
 
781
- .section-label {
782
- font-size: 10px;
783
- font-weight: 500;
784
- text-transform: uppercase;
785
- letter-spacing: 0.4px;
786
- color: var(--text3);
763
+ .provide-graph__detail-title {
764
+ font-size: var(--tracker-font-size-md);
765
+ }
766
+
767
+ .provide-graph__section-label {
787
768
  margin: 8px 0 5px;
788
769
  }
789
770
 
@@ -798,7 +779,6 @@ const edges = computed<Edge[]>(() => {
798
779
  flex-direction: column;
799
780
  gap: 3px;
800
781
  overflow: auto;
801
- max-height: 220px;
802
782
  padding-right: 2px;
803
783
  }
804
784
 
@@ -813,11 +793,20 @@ const edges = computed<Edge[]>(() => {
813
793
  }
814
794
 
815
795
  .row-warning {
816
- font-size: 11px;
796
+ font-size: var(--tracker-font-size-sm);
817
797
  color: var(--amber);
818
798
  padding: 2px 0;
819
799
  }
820
800
 
801
+ .provide-graph__compact-muted {
802
+ padding: 2px 0;
803
+ font-size: var(--tracker-font-size-sm);
804
+ }
805
+
806
+ .provide-graph__empty-detail {
807
+ margin-top: var(--tracker-space-2);
808
+ }
809
+
821
810
  .row-consumers {
822
811
  display: flex;
823
812
  flex-wrap: wrap;
@@ -862,7 +851,8 @@ const edges = computed<Edge[]>(() => {
862
851
  .row-main {
863
852
  display: flex;
864
853
  align-items: center;
865
- gap: 8px;
854
+ flex-wrap: wrap;
855
+ gap: 4px 8px;
866
856
  min-width: 0;
867
857
  }
868
858