nuxt-devtools-observatory 0.1.32 → 0.1.34

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 (36) hide show
  1. package/README.md +37 -1
  2. package/client/.env.example +2 -0
  3. package/client/dist/assets/index-BO7neKEi.css +1 -0
  4. package/client/dist/assets/index-fFBuk6M6.js +20 -0
  5. package/client/dist/index.html +2 -2
  6. package/client/src/App.vue +8 -0
  7. package/client/src/components/Flamegraph.vue +4 -4
  8. package/client/src/components/SpanInspector.vue +1 -1
  9. package/client/src/composables/composable-search.ts +3 -0
  10. package/client/src/composables/trace-render-aggregation.ts +11 -2
  11. package/client/src/composables/useVirtualizationConfig.ts +40 -0
  12. package/client/src/composables/useVirtualizationFlags.ts +129 -0
  13. package/client/src/stores/observatory.ts +20 -0
  14. package/client/src/views/ComposableTracker.vue +212 -71
  15. package/client/src/views/FetchDashboard.vue +181 -16
  16. package/client/src/views/PiniaStoreTracker.vue +343 -0
  17. package/client/src/views/ProvideInjectGraph.vue +66 -18
  18. package/client/src/views/RenderHeatmap.vue +329 -75
  19. package/client/src/views/TraceViewer.vue +190 -20
  20. package/client/src/views/TransitionTimeline.vue +112 -19
  21. package/dist/module.d.mts +15 -0
  22. package/dist/module.json +1 -1
  23. package/dist/module.mjs +28 -24
  24. package/dist/runtime/composables/pinia-store-registry.d.ts +44 -0
  25. package/dist/runtime/composables/pinia-store-registry.js +447 -0
  26. package/dist/runtime/composables/provide-inject-registry.js +13 -8
  27. package/dist/runtime/composables/render-registry.js +6 -4
  28. package/dist/runtime/instrumentation/asyncData.d.ts +1 -1
  29. package/dist/runtime/instrumentation/fetch.d.ts +7 -1
  30. package/dist/runtime/instrumentation/fetch.js +22 -1
  31. package/dist/runtime/plugin.js +39 -2
  32. package/dist/runtime/test-bridge.d.ts +18 -0
  33. package/dist/runtime/test-bridge.js +100 -0
  34. package/package.json +14 -3
  35. package/client/dist/assets/index-5Wl1XYRH.js +0 -17
  36. package/client/dist/assets/index-DT_QUiIh.css +0 -1
@@ -4,14 +4,17 @@ import { useObservatoryData } from './stores/observatory'
4
4
  import FetchDashboard from './views/FetchDashboard.vue'
5
5
  import ProvideInjectGraph from './views/ProvideInjectGraph.vue'
6
6
  import ComposableTracker from './views/ComposableTracker.vue'
7
+ import PiniaStoreTracker from './views/PiniaStoreTracker.vue'
7
8
  import RenderHeatmap from './views/RenderHeatmap.vue'
8
9
  import TransitionTimeline from './views/TransitionTimeline.vue'
9
10
  import TraceViewer from './views/TraceViewer.vue'
11
+ import { useVirtualizationFlags } from './composables/useVirtualizationFlags'
10
12
 
11
13
  const pathMap: Record<string, string> = {
12
14
  fetch: 'fetch',
13
15
  provide: 'provide',
14
16
  composables: 'composable',
17
+ pinia: 'pinia',
15
18
  heatmap: 'heatmap',
16
19
  transitions: 'transitions',
17
20
  traces: 'traces',
@@ -22,12 +25,16 @@ const activeTab = ref(pathMap[segment] ?? 'fetch')
22
25
 
23
26
  const { features } = useObservatoryData()
24
27
 
28
+ // Initializes rollout flags from query params before view rendering.
29
+ useVirtualizationFlags()
30
+
25
31
  const tabs = computed(() => {
26
32
  const f = features.value || {}
27
33
  return [
28
34
  f.fetchDashboard && { id: 'fetch', label: 'useFetch', icon: '⬡' },
29
35
  f.provideInjectGraph && { id: 'provide', label: 'provide/inject', icon: '⬡' },
30
36
  f.composableTracker && { id: 'composable', label: 'Composables', icon: '⬡' },
37
+ f.piniaTracker && { id: 'pinia', label: 'Pinia', icon: '⬡' },
31
38
  f.renderHeatmap && { id: 'heatmap', label: 'Heatmap', icon: '⬡' },
32
39
  f.transitionTracker && { id: 'transitions', label: 'Transitions', icon: '⬡' },
33
40
  f.traceViewer && { id: 'traces', label: 'Traces', icon: '⬡' },
@@ -49,6 +56,7 @@ const tabs = computed(() => {
49
56
  <FetchDashboard v-if="activeTab === 'fetch'" />
50
57
  <ProvideInjectGraph v-else-if="activeTab === 'provide'" />
51
58
  <ComposableTracker v-else-if="activeTab === 'composable'" />
59
+ <PiniaStoreTracker v-else-if="activeTab === 'pinia'" />
52
60
  <RenderHeatmap v-else-if="activeTab === 'heatmap'" />
53
61
  <TransitionTimeline v-else-if="activeTab === 'transitions'" />
54
62
  <TraceViewer v-else-if="activeTab === 'traces'" />
@@ -291,10 +291,6 @@ const flattenedTree = computed(() => {
291
291
  background: var(--tracker-tint-purple-soft, rgb(127 119 221 / 8%));
292
292
  }
293
293
 
294
- .flamegraph__row--selected .flamegraph__span-name {
295
- color: var(--purple, var(--accent));
296
- }
297
-
298
294
  .flamegraph__label {
299
295
  display: flex;
300
296
  align-items: center;
@@ -350,6 +346,10 @@ const flattenedTree = computed(() => {
350
346
  color: var(--text);
351
347
  }
352
348
 
349
+ .flamegraph__row--selected .flamegraph__span-name {
350
+ color: var(--purple, var(--accent));
351
+ }
352
+
353
353
  .flamegraph__span-type {
354
354
  padding: 2px 6px;
355
355
  background: var(--bg2, var(--bg));
@@ -404,7 +404,7 @@ function getSpanColorClass(type: string) {
404
404
  overflow: auto;
405
405
  max-height: 200px;
406
406
  white-space: pre-wrap;
407
- word-break: break-word;
407
+ overflow-wrap: anywhere;
408
408
  }
409
409
 
410
410
  /* Color utilities */
@@ -91,6 +91,9 @@ function valueMatchesQuery(value: unknown, query: string, seen: WeakSet<object>,
91
91
  /**
92
92
  * Returns true when a composable entry matches the search query.
93
93
  * Search scope includes name, file, ref keys, and nested reactive key/value content.
94
+ * @param {ComposableEntry} entry - Composable entry to inspect.
95
+ * @param {string} query - Case-insensitive search query.
96
+ * @returns {boolean} True when the entry matches the query.
94
97
  */
95
98
  export function matchesComposableEntryQuery(entry: ComposableEntry, query: string): boolean {
96
99
  const normalizedQuery = query.trim().toLowerCase()
@@ -68,7 +68,11 @@ function getComponentIdentity(metadata: SpanMetadata, fallbackId: string): { key
68
68
  }
69
69
  }
70
70
 
71
- /** Build per-trace render stats grouped by component UID. */
71
+ /**
72
+ * Build per-trace render stats grouped by component UID.
73
+ * @param {TraceEntry | undefined} trace - Trace to summarize.
74
+ * @returns {TraceRenderStatsRow[]} Render metrics grouped by component uid.
75
+ */
72
76
  export function buildRenderSummaryForTrace(trace?: TraceEntry): TraceRenderStatsRow[] {
73
77
  if (!trace) {
74
78
  return []
@@ -135,7 +139,12 @@ export function buildRenderSummaryForTrace(trace?: TraceEntry): TraceRenderStats
135
139
  return rows
136
140
  }
137
141
 
138
- /** Build cross-trace render aggregation and comparison against a selected trace. */
142
+ /**
143
+ * Build cross-trace render aggregation and comparison against a selected trace.
144
+ * @param {TraceEntry[]} traces - Traces included in the aggregation window.
145
+ * @param {string | undefined} selectedTraceId - Optional selected trace for baseline comparison.
146
+ * @returns {CrossTraceRenderSummaryRow[]} Aggregated comparison rows by component identity.
147
+ */
139
148
  export function buildCrossTraceRenderSummary(traces: TraceEntry[], selectedTraceId?: string): CrossTraceRenderSummaryRow[] {
140
149
  if (traces.length === 0) {
141
150
  return []
@@ -0,0 +1,40 @@
1
+ import { computed, ref } from 'vue'
2
+
3
+ const DEFAULT_ROW_HEIGHT = 34
4
+ const DEFAULT_OVERSCAN = 10
5
+ const MIN_OVERSCAN = 2
6
+ const MAX_OVERSCAN = 40
7
+
8
+ function clampOverscan(value: number): number {
9
+ if (!Number.isFinite(value)) {
10
+ return DEFAULT_OVERSCAN
11
+ }
12
+
13
+ return Math.max(MIN_OVERSCAN, Math.min(MAX_OVERSCAN, Math.round(value)))
14
+ }
15
+
16
+ export type VirtualizationPreset = {
17
+ rowHeight: number
18
+ overscan: number
19
+ }
20
+
21
+ export function useVirtualizationConfig(initial?: Partial<VirtualizationPreset>) {
22
+ const rowHeight = ref(initial?.rowHeight ?? DEFAULT_ROW_HEIGHT)
23
+ const overscan = ref(clampOverscan(initial?.overscan ?? DEFAULT_OVERSCAN))
24
+
25
+ const preset = computed<VirtualizationPreset>(() => ({
26
+ rowHeight: Math.max(20, rowHeight.value),
27
+ overscan: clampOverscan(overscan.value),
28
+ }))
29
+
30
+ return {
31
+ rowHeight,
32
+ overscan,
33
+ preset,
34
+ }
35
+ }
36
+
37
+ export const VIRTUALIZATION_DEFAULTS = {
38
+ rowHeight: DEFAULT_ROW_HEIGHT,
39
+ overscan: DEFAULT_OVERSCAN,
40
+ } as const
@@ -0,0 +1,129 @@
1
+ import { computed, ref } from 'vue'
2
+
3
+ type VirtualizationScreen = 'heatmap' | 'traces' | 'composables' | 'fetch' | 'transitions'
4
+
5
+ type VirtualizationFlags = {
6
+ enabled: boolean
7
+ heatmap: boolean
8
+ traces: boolean
9
+ composables: boolean
10
+ fetch: boolean
11
+ transitions: boolean
12
+ }
13
+
14
+ const defaultFlags: VirtualizationFlags = {
15
+ enabled: false,
16
+ heatmap: false,
17
+ traces: false,
18
+ composables: false,
19
+ fetch: false,
20
+ transitions: false,
21
+ }
22
+
23
+ const flags = ref<VirtualizationFlags>({ ...defaultFlags })
24
+
25
+ let initialized = false
26
+
27
+ function parseBooleanParam(value: string | null): boolean | null {
28
+ if (value == null) {
29
+ return null
30
+ }
31
+
32
+ if (value === '1' || value === 'true' || value === 'on') {
33
+ return true
34
+ }
35
+
36
+ if (value === '0' || value === 'false' || value === 'off') {
37
+ return false
38
+ }
39
+
40
+ return null
41
+ }
42
+
43
+ function readFromStorage(): VirtualizationFlags {
44
+ return { ...defaultFlags }
45
+ }
46
+
47
+ function applyQueryOverrides(current: VirtualizationFlags): VirtualizationFlags {
48
+ if (typeof window === 'undefined') {
49
+ return current
50
+ }
51
+
52
+ const params = new URLSearchParams(window.location.search)
53
+ const globalParam = parseBooleanParam(params.get('virt'))
54
+
55
+ const next = { ...current }
56
+
57
+ if (globalParam != null) {
58
+ next.enabled = globalParam
59
+ }
60
+
61
+ const perScreen: Array<[VirtualizationScreen, string]> = [
62
+ ['heatmap', 'virtHeatmap'],
63
+ ['traces', 'virtTraces'],
64
+ ['composables', 'virtComposables'],
65
+ ['fetch', 'virtFetch'],
66
+ ['transitions', 'virtTransitions'],
67
+ ]
68
+
69
+ for (const [key, paramName] of perScreen) {
70
+ const override = parseBooleanParam(params.get(paramName))
71
+
72
+ if (override != null) {
73
+ next[key] = override
74
+ }
75
+ }
76
+
77
+ return next
78
+ }
79
+
80
+ function init() {
81
+ if (initialized) {
82
+ return
83
+ }
84
+
85
+ initialized = true
86
+ const stored = readFromStorage()
87
+ const merged = applyQueryOverrides(stored)
88
+ flags.value = merged
89
+ }
90
+
91
+ function setAllEnabled(value: boolean) {
92
+ const next = {
93
+ ...flags.value,
94
+ enabled: value,
95
+ }
96
+
97
+ flags.value = next
98
+ }
99
+
100
+ function setScreenEnabled(screen: VirtualizationScreen, value: boolean) {
101
+ const next = {
102
+ ...flags.value,
103
+ [screen]: value,
104
+ }
105
+
106
+ flags.value = next
107
+ }
108
+
109
+ export function useVirtualizationFlags() {
110
+ init()
111
+
112
+ const effective = computed(() => ({
113
+ enabled: flags.value.enabled,
114
+ heatmap: flags.value.enabled && flags.value.heatmap,
115
+ traces: flags.value.enabled && flags.value.traces,
116
+ composables: flags.value.enabled && flags.value.composables,
117
+ fetch: flags.value.enabled && flags.value.fetch,
118
+ transitions: flags.value.enabled && flags.value.transitions,
119
+ }))
120
+
121
+ return {
122
+ flags,
123
+ effective,
124
+ setAllEnabled,
125
+ setScreenEnabled,
126
+ }
127
+ }
128
+
129
+ export type { VirtualizationFlags, VirtualizationScreen }
@@ -6,6 +6,7 @@ import type {
6
6
  ProvideEntry,
7
7
  InjectEntry,
8
8
  ComposableEntry,
9
+ PiniaStoreEntry,
9
10
  RenderEntry,
10
11
  TransitionEntry,
11
12
  TraceEntry,
@@ -16,6 +17,7 @@ type ProvideInjectSnapshot = { provides: ProvideEntry[]; injects: InjectEntry[]
16
17
  const fetchEntries = ref<FetchEntry[]>([])
17
18
  const provideInject = ref<ProvideInjectSnapshot>({ provides: [], injects: [] })
18
19
  const composables = ref<ComposableEntry[]>([])
20
+ const piniaStores = ref<PiniaStoreEntry[]>([])
19
21
  const renders = ref<RenderEntry[]>([])
20
22
  const transitions = ref<TransitionEntry[]>([])
21
23
  const traces = ref<TraceEntry[]>([])
@@ -57,6 +59,7 @@ function applySnapshot(data: ObservatorySnapshot) {
57
59
  }
58
60
  : { provides: [], injects: [] }
59
61
  composables.value = cloneArray(data.composables as ComposableEntry[] | undefined)
62
+ piniaStores.value = cloneArray(data.piniaStores as PiniaStoreEntry[] | undefined)
60
63
  renders.value = normalizeRenderEntries(data.renders as RenderEntry[] | undefined)
61
64
  transitions.value = cloneArray(data.transitions as TransitionEntry[] | undefined)
62
65
  traces.value = cloneArray(data.traces as TraceEntry[] | undefined)
@@ -87,6 +90,7 @@ function applySnapshot(data: ObservatorySnapshot) {
87
90
  debugLog('first snapshot received', {
88
91
  fetch: fetchEntries.value.length,
89
92
  composables: composables.value.length,
93
+ piniaStores: piniaStores.value.length,
90
94
  renders: renders.value.length,
91
95
  transitions: transitions.value.length,
92
96
  traces: traces.value.length,
@@ -200,6 +204,21 @@ export function editComposableValue(id: string, key: string, value: unknown) {
200
204
  })
201
205
  }
202
206
 
207
+ export function clearPiniaStores() {
208
+ piniaStores.value = []
209
+ rpc?.clearPiniaStores()
210
+ .then(() => rpc?.requestSnapshot())
211
+ .catch((error) => {
212
+ debugLog('clearPiniaStores failed', error)
213
+ })
214
+ }
215
+
216
+ export function editPiniaState(storeId: string, path: string, value: unknown) {
217
+ rpc?.editPiniaState(storeId, path, value).catch((error) => {
218
+ debugLog('editPiniaState failed', error)
219
+ })
220
+ }
221
+
203
222
  export function openInEditor(file: string) {
204
223
  if (!file || file === 'unknown') {
205
224
  return
@@ -233,6 +252,7 @@ export function useObservatoryData() {
233
252
  fetch: fetchEntries,
234
253
  provideInject,
235
254
  composables,
255
+ piniaStores,
236
256
  renders,
237
257
  transitions,
238
258
  traces,