nuxt-devtools-observatory 0.1.22 → 0.1.24

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.
@@ -31,14 +31,50 @@ function formatVal(v: unknown): string {
31
31
  return String(v)
32
32
  }
33
33
 
34
+ /**
35
+ * Full (non-truncated) formatter used in the expanded detail rows.
36
+ * @param {unknown} v - The value to format.
37
+ * @returns {string} Pretty-printed JSON or primitive string.
38
+ */
39
+ function formatValFull(v: unknown): string {
40
+ if (v === null) {
41
+ return 'null'
42
+ }
43
+
44
+ if (v === undefined) {
45
+ return 'undefined'
46
+ }
47
+
48
+ if (typeof v === 'string') {
49
+ return `"${v}"`
50
+ }
51
+
52
+ if (typeof v === 'object') {
53
+ try {
54
+ return JSON.stringify(v, null, 2)
55
+ } catch {
56
+ return String(v)
57
+ }
58
+ }
59
+
60
+ return String(v)
61
+ }
62
+
34
63
  function basename(file: string) {
35
64
  return file.split('/').pop() ?? file
36
65
  }
37
66
 
38
67
  function openInEditor(file: string) {
39
- if (!file || file === 'unknown') return
68
+ if (!file || file === 'unknown') {
69
+ return
70
+ }
71
+
40
72
  const origin = getObservatoryOrigin()
41
- if (!origin) return
73
+
74
+ if (!origin) {
75
+ return
76
+ }
77
+
42
78
  window.top?.postMessage({ type: 'observatory:open-in-editor', file }, origin)
43
79
  }
44
80
 
@@ -55,10 +91,20 @@ const counts = computed(() => ({
55
91
 
56
92
  const filtered = computed(() => {
57
93
  return entries.value.filter((entry) => {
58
- if (filter.value === 'leak' && !entry.leak) return false
59
- if (filter.value === 'mounted' && entry.status !== 'mounted') return false
60
- if (filter.value === 'unmounted' && entry.status !== 'unmounted') return false
94
+ if (filter.value === 'leak' && !entry.leak) {
95
+ return false
96
+ }
97
+
98
+ if (filter.value === 'mounted' && entry.status !== 'mounted') {
99
+ return false
100
+ }
101
+
102
+ if (filter.value === 'unmounted' && entry.status !== 'unmounted') {
103
+ return false
104
+ }
105
+
61
106
  const q = search.value.toLowerCase()
107
+
62
108
  if (q) {
63
109
  const matchesName = entry.name.toLowerCase().includes(q)
64
110
  const matchesFile = entry.componentFile.toLowerCase().includes(q)
@@ -70,8 +116,12 @@ const filtered = computed(() => {
70
116
  return false
71
117
  }
72
118
  })
73
- if (!matchesName && !matchesFile && !matchesRef && !matchesVal) return false
119
+
120
+ if (!matchesName && !matchesFile && !matchesRef && !matchesVal) {
121
+ return false
122
+ }
74
123
  }
124
+
75
125
  return true
76
126
  })
77
127
  })
@@ -102,19 +152,61 @@ function lifecycleRows(entry: RuntimeComposableEntry) {
102
152
  }
103
153
 
104
154
  function typeBadgeClass(type: string) {
105
- if (type === 'computed') return 'badge-info'
106
- if (type === 'reactive') return 'badge-purple'
155
+ if (type === 'computed') {
156
+ return 'badge-info'
157
+ }
158
+
159
+ if (type === 'reactive') {
160
+ return 'badge-purple'
161
+ }
162
+
107
163
  return 'badge-gray'
108
164
  }
109
165
 
166
+ // ── Collapsible ref values ──────────────────────────────────────────────
167
+ // Long values (objects/arrays) start collapsed inside the expanded card.
168
+ // Clicking the toggle chevron expands/collapses them inline.
169
+
170
+ /** Keys that are currently expanded: "<entryId>:<refKey>" */
171
+ const expandedRefs = ref<Set<string>>(new Set())
172
+
173
+ function refExpandKey(entryId: string, refKey: string) {
174
+ return `${entryId}:${refKey}`
175
+ }
176
+
177
+ function isLongValue(v: unknown): boolean {
178
+ if (v === null || v === undefined || typeof v !== 'object') return false
179
+ try {
180
+ return JSON.stringify(v).length > 60
181
+ } catch {
182
+ return false
183
+ }
184
+ }
185
+
186
+ function isRefExpanded(entryId: string, refKey: string): boolean {
187
+ return expandedRefs.value.has(refExpandKey(entryId, refKey))
188
+ }
189
+
190
+ function toggleRefExpand(entryId: string, refKey: string) {
191
+ const key = refExpandKey(entryId, refKey)
192
+ const next = new Set(expandedRefs.value)
193
+ if (next.has(key)) next.delete(key)
194
+ else next.add(key)
195
+ expandedRefs.value = next
196
+ }
197
+
110
198
  // ── Reverse lookup ────────────────────────────────────────────────────────
111
199
  // Clicking a ref key shows every mounted instance that exposes the same key.
112
200
 
113
201
  const lookupKey = ref<string | null>(null)
114
202
 
115
203
  const lookupResults = computed(() => {
116
- if (!lookupKey.value) return []
204
+ if (!lookupKey.value) {
205
+ return []
206
+ }
207
+
117
208
  const key = lookupKey.value
209
+
118
210
  return entries.value.filter((e) => key in e.refs)
119
211
  })
120
212
 
@@ -144,7 +236,9 @@ function openEdit(id: string, key: string, currentValue: unknown) {
144
236
  }
145
237
 
146
238
  function applyEdit() {
147
- if (!editTarget.value) return
239
+ if (!editTarget.value) {
240
+ return
241
+ }
148
242
 
149
243
  let parsed: unknown
150
244
 
@@ -157,7 +251,10 @@ function applyEdit() {
157
251
  }
158
252
 
159
253
  const origin = getObservatoryOrigin()
160
- if (!origin) return
254
+
255
+ if (!origin) {
256
+ return
257
+ }
161
258
 
162
259
  window.top?.postMessage(
163
260
  {
@@ -273,12 +370,35 @@ function applyEdit() {
273
370
  >
274
371
  {{ k }}
275
372
  </span>
276
- <span class="mono text-sm ref-val">{{ formatVal(v.value) }}</span>
277
- <span class="badge text-xs" :class="typeBadgeClass(v.type)">{{ v.type }}</span>
278
- <span v-if="entry.sharedKeys?.includes(k)" class="badge badge-amber text-xs">global</span>
279
- <button v-if="v.type === 'ref'" class="edit-btn" title="Edit value" @click.stop="openEdit(entry.id, k, v.value)">
280
- edit
281
- </button>
373
+ <span
374
+ class="mono text-sm ref-val"
375
+ :class="{
376
+ 'ref-val--full': isLongValue(v.value) && isRefExpanded(entry.id, k),
377
+ 'ref-val--collapsed': isLongValue(v.value) && !isRefExpanded(entry.id, k),
378
+ }"
379
+ >
380
+ {{ isLongValue(v.value) && !isRefExpanded(entry.id, k) ? formatVal(v.value) : formatValFull(v.value) }}
381
+ </span>
382
+ <div class="ref-row-actions">
383
+ <button
384
+ v-if="isLongValue(v.value)"
385
+ class="expand-btn"
386
+ :title="isRefExpanded(entry.id, k) ? 'Collapse' : 'Expand'"
387
+ @click.stop="toggleRefExpand(entry.id, k)"
388
+ >
389
+ {{ isRefExpanded(entry.id, k) ? '▲' : '▼' }}
390
+ </button>
391
+ <span class="badge text-xs" :class="typeBadgeClass(v.type)">{{ v.type }}</span>
392
+ <span v-if="entry.sharedKeys?.includes(k)" class="badge badge-amber text-xs">global</span>
393
+ <button
394
+ v-if="v.type === 'ref'"
395
+ class="edit-btn"
396
+ title="Edit value"
397
+ @click.stop="openEdit(entry.id, k, v.value)"
398
+ >
399
+ edit
400
+ </button>
401
+ </div>
282
402
  </div>
283
403
 
284
404
  <template v-if="entry.history && entry.history.length">
@@ -569,7 +689,7 @@ function applyEdit() {
569
689
 
570
690
  .ref-row {
571
691
  display: flex;
572
- align-items: center;
692
+ align-items: flex-start;
573
693
  gap: 8px;
574
694
  padding: 3px 0;
575
695
  }
@@ -582,10 +702,44 @@ function applyEdit() {
582
702
 
583
703
  .ref-val {
584
704
  flex: 1;
705
+ color: var(--teal);
706
+ min-width: 0;
707
+ }
708
+
709
+ .ref-val--collapsed {
585
710
  overflow: hidden;
586
711
  text-overflow: ellipsis;
587
712
  white-space: nowrap;
588
- color: var(--teal);
713
+ }
714
+
715
+ .ref-val--full {
716
+ white-space: pre-wrap;
717
+ word-break: break-all;
718
+ line-height: 1.5;
719
+ }
720
+
721
+ .ref-row-actions {
722
+ display: flex;
723
+ align-items: center;
724
+ gap: 4px;
725
+ flex-shrink: 0;
726
+ }
727
+
728
+ .expand-btn {
729
+ font-size: 9px;
730
+ padding: 1px 5px;
731
+ border-radius: 4px;
732
+ border: 0.5px solid var(--border);
733
+ background: var(--bg2);
734
+ color: var(--text3);
735
+ cursor: pointer;
736
+ line-height: 1.4;
737
+ flex-shrink: 0;
738
+ }
739
+
740
+ .expand-btn:hover {
741
+ border-color: var(--text3);
742
+ color: var(--text);
589
743
  }
590
744
 
591
745
  .lc-row {
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.0.0 || ^4.0.0"
6
6
  },
7
- "version": "0.1.22",
7
+ "version": "0.1.24",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -543,29 +543,30 @@ function transitionTrackerPlugin() {
543
543
  };
544
544
  }
545
545
 
546
+ const defaults = {
547
+ instrumentServer: process.env.OBSERVATORY_INSTRUMENT_SERVER === "true",
548
+ fetchDashboard: process.env.OBSERVATORY_FETCH_DASHBOARD === "true",
549
+ provideInjectGraph: process.env.OBSERVATORY_PROVIDE_INJECT_GRAPH === "true",
550
+ composableTracker: process.env.OBSERVATORY_COMPOSABLE_TRACKER === "true",
551
+ renderHeatmap: process.env.OBSERVATORY_RENDER_HEATMAP === "true",
552
+ transitionTracker: process.env.OBSERVATORY_TRANSITION_TRACKER === "true",
553
+ heatmapThresholdCount: process.env.OBSERVATORY_HEATMAP_THRESHOLD_COUNT ? Number(process.env.OBSERVATORY_HEATMAP_THRESHOLD_COUNT) : 3,
554
+ heatmapThresholdTime: process.env.OBSERVATORY_HEATMAP_THRESHOLD_TIME ? Number(process.env.OBSERVATORY_HEATMAP_THRESHOLD_TIME) : 1600,
555
+ maxFetchEntries: process.env.OBSERVATORY_MAX_FETCH_ENTRIES ? Number(process.env.OBSERVATORY_MAX_FETCH_ENTRIES) : 200,
556
+ maxPayloadBytes: process.env.OBSERVATORY_MAX_PAYLOAD_BYTES ? Number(process.env.OBSERVATORY_MAX_PAYLOAD_BYTES) : 1e4,
557
+ maxTransitions: process.env.OBSERVATORY_MAX_TRANSITIONS ? Number(process.env.OBSERVATORY_MAX_TRANSITIONS) : 500,
558
+ maxComposableHistory: process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY) : 50,
559
+ maxComposableEntries: process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES) : 300,
560
+ maxRenderTimeline: process.env.OBSERVATORY_MAX_RENDER_TIMELINE ? Number(process.env.OBSERVATORY_MAX_RENDER_TIMELINE) : 100,
561
+ heatmapHideInternals: process.env.OBSERVATORY_HEATMAP_HIDE_INTERNALS === "true"
562
+ };
546
563
  const module$1 = defineNuxtModule({
547
564
  meta: {
548
565
  name: "nuxt-devtools-observatory",
549
566
  configKey: "observatory",
550
567
  compatibility: { nuxt: "^3.0.0 || ^4.0.0" }
551
568
  },
552
- defaults: {
553
- instrumentServer: process.env.OBSERVATORY_INSTRUMENT_SERVER === "true",
554
- fetchDashboard: process.env.OBSERVATORY_FETCH_DASHBOARD === "true",
555
- provideInjectGraph: process.env.OBSERVATORY_PROVIDE_INJECT_GRAPH === "true",
556
- composableTracker: process.env.OBSERVATORY_COMPOSABLE_TRACKER === "true",
557
- renderHeatmap: process.env.OBSERVATORY_RENDER_HEATMAP === "true",
558
- transitionTracker: process.env.OBSERVATORY_TRANSITION_TRACKER === "true",
559
- heatmapThresholdCount: process.env.OBSERVATORY_HEATMAP_THRESHOLD_COUNT ? Number(process.env.OBSERVATORY_HEATMAP_THRESHOLD_COUNT) : 3,
560
- heatmapThresholdTime: process.env.OBSERVATORY_HEATMAP_THRESHOLD_TIME ? Number(process.env.OBSERVATORY_HEATMAP_THRESHOLD_TIME) : 1600,
561
- maxFetchEntries: process.env.OBSERVATORY_MAX_FETCH_ENTRIES ? Number(process.env.OBSERVATORY_MAX_FETCH_ENTRIES) : 200,
562
- maxPayloadBytes: process.env.OBSERVATORY_MAX_PAYLOAD_BYTES ? Number(process.env.OBSERVATORY_MAX_PAYLOAD_BYTES) : 1e4,
563
- maxTransitions: process.env.OBSERVATORY_MAX_TRANSITIONS ? Number(process.env.OBSERVATORY_MAX_TRANSITIONS) : 500,
564
- maxComposableHistory: process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY) : 50,
565
- maxComposableEntries: process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES) : 300,
566
- maxRenderTimeline: process.env.OBSERVATORY_MAX_RENDER_TIMELINE ? Number(process.env.OBSERVATORY_MAX_RENDER_TIMELINE) : 100,
567
- heatmapHideInternals: process.env.OBSERVATORY_HEATMAP_HIDE_INTERNALS === "true"
568
- },
569
+ defaults,
569
570
  setup(options, nuxt) {
570
571
  if (!nuxt.options.dev) {
571
572
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-devtools-observatory",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "Nuxt DevTools: useFetch Dashboard, provide/inject Graph, Composable Tracker, Render Heatmap",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -45,6 +45,7 @@
45
45
  "test:watch": "vitest",
46
46
  "test:coverage": "vitest run --coverage",
47
47
  "test:screenshots": "playwright test scripts/playwright/screenshot-trackers.spec.ts",
48
+ "test:e2e": "playwright test scripts/playwright/playground-pages.spec.ts",
48
49
  "capture:screenshots": "node scripts/playwright/capture-observatory-screenshots.cjs"
49
50
  },
50
51
  "dependencies": {
@@ -59,6 +60,7 @@
59
60
  "@nuxt/kit": "^3.0.0",
60
61
  "@nuxt/module-builder": "^1.0.2",
61
62
  "@nuxt/schema": "^3.0.0",
63
+ "@pinia/nuxt": "^0.11.3",
62
64
  "@playwright/test": "^1.58.2",
63
65
  "@types/babel__generator": "^7.27.0",
64
66
  "@types/babel__traverse": "^7.28.0",
@@ -74,6 +76,7 @@
74
76
  "globals": "^17.3.0",
75
77
  "happy-dom": "^20.8.4",
76
78
  "nuxt": "^3.0.0 || ^4.0.0",
79
+ "pinia": "^3.0.4",
77
80
  "playwright": "^1.58.2",
78
81
  "postcss-html": "^1.8.1",
79
82
  "prettier": "^3.8.1",