nuxt-devtools-observatory 0.1.31 → 0.1.32

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.
package/README.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  # Nuxt DevTools Observatory
4
4
 
5
+ > [!WARNING]
6
+ > **Performance note:** Instrumentation wraps every useFetch, composable, and lifecycle hook. In large apps this can slow down the DevTools UI or increase memory usage. Disable unused features via the observatory config.
7
+
5
8
  Nuxt DevTools module providing six missing observability features:
6
9
 
7
10
  - **useFetch Dashboard** — central view of all async data calls, cache keys, waterfall timeline
@@ -162,27 +165,22 @@ wraps them with a tracking shim (`__trackComposable`) that:
162
165
  The panel provides:
163
166
 
164
167
  - **Navigation mode toggle** — switch between 'route' mode (clears entries on navigation) and 'session' mode (persists entries across pages). In session mode, a "clear session" button appears to manually reset the history
165
- - **Filtering** by status (all / mounted / unmounted / leaks only) and free-text search across composable name, source file, ref key names, and ref values
168
+ - **Filtering** by status (all / mounted / unmounted / leaks only) and free-text search across composable name, source file, ref key names, ref values, and nested reactive object properties
166
169
  - **Recency-first ordering** — newest entries appear first, with layout-level composables pinned to the top (layout composables persist across page navigation)
167
170
  - **Inline ref chip preview** — up to three reactive values shown on the card without expanding, with distinct styling for `ref`, `computed`, and `reactive` types
168
171
  - **Collapsible ref values** — long objects and arrays automatically collapse with a chevron toggle to expand them inline in the detail view with full pretty-printed JSON
169
172
  - **Global state badges** — keys shared across instances are highlighted in amber with a `global` badge and an explanatory banner when expanded
170
173
  - **Change history** — a scrollable log of the last 50 value mutations with key, new value, and relative timestamp
171
174
  - **Lifecycle summary** — shows whether `onMounted`/`onUnmounted` were registered and whether watchers and intervals were properly cleaned up
172
- - **Reverse lookup** — clicking any ref key opens a panel listing every other composable instance that exposes a key with the same name, with its composable name, file, and route
175
+ - **Reverse lookup** — clicking any ref key opens a panel listing related composable instances. For shared/global refs it matches by shared object identity; otherwise it falls back to key-name matching
173
176
  - **Inline value editing** — writable `ref` values have an `edit` button; clicking opens a JSON textarea that applies the new value directly to the live ref in the running app
174
177
  - **Jump to editor** — an `open ↗` button in the context section opens the composable's source file in the configured editor
175
178
 
176
- **Known gaps:**
177
-
178
- - Reverse lookup matches by key name only, not by object identity
179
- - Search does not look inside nested `reactive` object properties
180
-
181
179
  ### Render Heatmap
182
180
 
183
181
  [![Render Heatmap](https://github.com/victorlmneves/nuxt-devtools-observatory/blob/main/docs/screenshots/render-heatmap.png)](https://github.com/victorlmneves/nuxt-devtools-observatory/blob/main/docs/screenshots/render-heatmap.png)
184
182
 
185
- Uses Vue's built-in `renderTriggered` mixin hook and `app.config.performance = true`.
183
+ Uses Vue lifecycle instrumentation with `app.config.performance = true`.
186
184
  Accurate duration is measured by bracketing each `beforeMount`/`mounted` and
187
185
  `beforeUpdate`/`updated` cycle with `performance.now()` timestamps.
188
186
  Component bounding boxes are captured via `$el.getBoundingClientRect()` for the DOM
@@ -194,8 +192,7 @@ last). Every mount and update cycle appends an event recording:
194
192
  - `kind` — `mount` or `update`
195
193
  - `t` — `performance.now()` timestamp
196
194
  - `durationMs` — measured render duration
197
- - `triggerKey` — the reactive dep that caused the update (when `renderTriggered` fired
198
- before `updated`), formatted as `type: key`
195
+ - `triggerKey` — optional reactive dep label for the update (when available)
199
196
  - `route` — the route path at the time of the render
200
197
 
201
198
  A `route` field on each entry records which route the component was first seen on.
@@ -224,6 +221,11 @@ The panel provides:
224
221
  panel's identity section has an `open ↗` button; both call Vite's built-in
225
222
  `/__open-in-editor` endpoint to open the component's source file in the configured
226
223
  editor
224
+ - **Export / Import** — `↓ export` downloads the current render data (live or frozen
225
+ snapshot) as a `.json` file; `↑ import` loads a previously exported file and
226
+ automatically enters freeze mode so the imported snapshot is displayed in place of
227
+ live data; the freeze button label changes to `unfreeze (imported)` to signal the
228
+ import state
227
229
 
228
230
  **Known gaps:**
229
231
 
@@ -241,14 +243,15 @@ after a route change.
241
243
 
242
244
  **Span types collected:**
243
245
 
244
- | Type | Emitted by | What it measures |
245
- | ------------ | --------------------------------- | ---------------------------------------------------- |
246
- | `navigation` | `router.afterEach` hook | Route change from → to, duration |
247
- | `component` | Vue mixin lifecycle hooks | Exact `mounted` / `updated` hook cost |
248
- | `render` | `beforeMount` → `mounted` bracket | Real DOM-patching time per component mount/update |
249
- | `fetch` | `useFetch` / `useAsyncData` shim | Network request start, server/client origin, latency |
250
- | `composable` | `__trackComposable` shim | Setup phase of every tracked `useXxx()` call |
251
- | `transition` | `<Transition>` wrapper | Full enter/leave lifecycle phase |
246
+ | Type | Emitted by | What it measures |
247
+ | ------------ | --------------------------------- | ------------------------------------------------------ |
248
+ | `navigation` | `router.afterEach` hook | Route change from → to, duration |
249
+ | `component` | Vue mixin lifecycle hooks | Exact `mounted` / `updated` hook cost |
250
+ | `render` | `beforeMount` → `mounted` bracket | Real DOM-patching time per component mount/update |
251
+ | `fetch` | `useFetch` / `useAsyncData` shim | Network request start, server/client origin, latency |
252
+ | `server` | Nitro SSR lifecycle hooks | Server-side phase timing (e.g. `render:html`) |
253
+ | `composable` | `__trackComposable` shim | Setup phase of tracked `useXxx()` calls (client + SSR) |
254
+ | `transition` | `<Transition>` wrapper | Full enter/leave lifecycle phase |
252
255
 
253
256
  **Render span tracking:**
254
257
  Real render time is measured by storing `performance.now()` in a `WeakMap<ComponentPublicInstance, number>` inside `beforeMount` / `beforeUpdate`, then reading it back in the corresponding `mounted` / `updated` hooks. This produces a `type: 'render'` span whose duration is the actual DOM-patching cost, separately from the `component:mounted` hook span (which only measures the hook body itself).
@@ -265,6 +268,14 @@ The panel provides:
265
268
  - **Filter panel** — filter by span type and free-text search across span names and metadata
266
269
  - **Duration labels** — spans narrower than 5 % of the timeline render their label outside the bar for readability
267
270
  - **In-progress traces** — active traces show `~Xms` (computed from the latest span end offset) rather than a hard "in progress" label
271
+ - **Export / Import** — `↓ export` downloads all captured traces as a `.json` file
272
+ (always exports the full unfiltered dataset); `↑ import` loads a previously exported
273
+ file and freezes the view on the imported data; a `← live` button returns to the
274
+ live stream; the trace count label shows `(imported)` while viewing imported data
275
+ - **Cross-trace render comparison** — compares render behavior across filtered traces,
276
+ with per-component average re-renders per trace, selected-trace value, and delta
277
+ versus baseline; includes sortable columns, quick filters (regressions/comparable),
278
+ in-table search, and condensed mobile columns
268
279
 
269
280
  **What it tells you:**
270
281
 
@@ -274,11 +285,6 @@ The panel provides:
274
285
  - Whether composable setup is adding meaningful latency
275
286
  - Which components are slow to mount and might benefit from `<Suspense>` or lazy loading
276
287
 
277
- **Known gaps:**
278
-
279
- - SSR spans are not yet captured — only client-side navigation traces are collected
280
- - Re-render counts are tracked by Render Heatmap; the Trace Viewer records individual render events but does not aggregate them
281
-
282
288
  ### Transition Tracker
283
289
 
284
290
  [![Transition Tracker](https://github.com/victorlmneves/nuxt-devtools-observatory/blob/main/docs/screenshots/transition-tracker.png)](https://github.com/victorlmneves/nuxt-devtools-observatory/blob/main/docs/screenshots/transition-tracker.png)
@@ -324,16 +330,7 @@ const result = useMyComposable()
324
330
 
325
331
  ## Roadmap
326
332
 
327
- ### Composable Tracker
328
-
329
- - [ ] Reverse lookup by object identity rather than key name only
330
- - [ ] Deep search inside nested `reactive` object properties
331
-
332
- ### Trace Viewer
333
-
334
- - [ ] SSR span collection (server-side navigation and composable setup)
335
- - [ ] Cross-trace comparison view
336
- - [ ] Export traces as JSON
333
+ - [x] Dedicated cross-trace comparison UI polish (sorting, stronger visual deltas, and quick filtering)
337
334
 
338
335
  ## Development
339
336
 
@@ -373,16 +370,26 @@ src/
373
370
  │ ├── provide-inject-transform.ts ← AST wraps provide/inject
374
371
  │ ├── composable-transform.ts ← AST wraps useX() composables
375
372
  │ └── transition-transform.ts ← Virtual vue proxy — overrides Transition export
376
- ├── runtime/
377
- ├── plugin.ts ← Client runtime bootstrap + RPC/Vite WS bridge
378
- │ └── composables/
379
- ├── fetch-registry.ts ← Fetch tracking store + __devFetch shim
380
- ├── provide-inject-registry.ts ← Injection tracking + __devProvide/__devInject
381
- ├── composable-registry.ts ← Composable tracking + __trackComposable + leak detection
382
- ├── render-registry.ts ← Render performance data via PerformanceObserver
383
- └── transition-registry.ts ← Transition lifecycle store
384
- └── nitro/
385
- └── fetch-capture.ts SSR-side fetch timing
373
+ └── runtime/
374
+ ├── plugin.ts ← Client runtime bootstrap + RPC/Vite WS bridge
375
+ ├── composables/
376
+ ├── fetch-registry.ts ← Fetch tracking store + __devFetch shim
377
+ ├── provide-inject-registry.ts ← Injection tracking + __devProvide/__devInject
378
+ ├── composable-registry.ts ← Composable tracking + __trackComposable + leak detection
379
+ ├── render-registry.ts ← Render registry (timeline, route attribution, bbox snapshots)
380
+ └── transition-registry.ts ← Transition lifecycle store
381
+ ├── instrumentation/
382
+ │ ├── route.ts router.afterEach hook — opens/closes traces per navigation
383
+ │ ├── component.ts ← Vue mixin lifecycle hooks — component + render spans
384
+ │ ├── fetch.ts ← useFetch/useAsyncData span recording
385
+ │ └── asyncData.ts ← useAsyncData-specific span handling
386
+ ├── tracing/
387
+ │ ├── trace.ts ← Span + Trace types (server-side internal)
388
+ │ ├── traceStore.ts ← In-memory Map<string, Trace> store
389
+ │ ├── tracing.ts ← Span open/close helpers
390
+ │ └── context.ts ← Current-trace context (per async task)
391
+ └── nitro/
392
+ └── fetch-capture.ts ← SSR request tracing bridge (fetch + server phases + context)
386
393
 
387
394
  client/
388
395
  ├── index.html
@@ -393,31 +400,52 @@ client/
393
400
  ├── main.ts
394
401
  ├── style.css ← Design system
395
402
  ├── components/
403
+ ├── composables/
404
+ │ ├── composable-search.ts ← Deep nested composable search matching utilities
405
+ │ ├── trace-render-aggregation.ts ← Per-trace and cross-trace render aggregation helpers
406
+ │ ├── useExportImport.ts ← JSON export (download) and import (file picker) utilities
407
+ │ ├── useResizablePane.ts ← Resizable split-pane drag handle
408
+ │ └── useTraceFilter.ts ← Trace filter state and filtering logic
396
409
  ├── stores/
397
410
  └── views/
398
411
  ├── FetchDashboard.vue ← useFetch tab UI
399
412
  ├── ProvideInjectGraph.vue ← provide/inject tab UI
400
413
  ├── ComposableTracker.vue ← Composable tab UI
414
+ ├── ComponentBlock.vue ← Shared component detail block
415
+ ├── ValueInspector.vue ← Inline JSON value inspector
401
416
  ├── RenderHeatmap.vue ← Heatmap tab UI
402
417
  ├── TransitionTimeline.vue ← Transition tracker tab UI
403
- └── TraceViewer.vue ← Trace viewer tab UI (Flamegraph + Waterfall + Inspector)
418
+ └── TraceViewer.vue ← Trace viewer tab UI (Overview + Flamegraph + Waterfall + Inspector + cross-trace comparison)
404
419
 
405
420
  playground/
406
421
  ├── app.vue ← Demo app shell used during local development
407
422
  ├── nuxt.config.ts
408
423
  ├── composables/
409
424
  │ ├── useCounter.ts ← Clean composable (properly cleaned up)
410
- └── useLeakyPoller.ts ← Intentionally leaky (for demo)
425
+ ├── useLeakyPoller.ts ← Intentionally leaky (for demo)
426
+ │ ├── useCart.ts ← Cart state composable
427
+ │ ├── useDashboard.ts ← Dashboard data composable
428
+ │ ├── useLeakyCartAudit.ts ← Leaky audit poller (for demo)
429
+ │ ├── usePersistentCartSummary.ts ← Cart summary across navigations
430
+ │ ├── useProductFilter.ts ← Product search/filter state
431
+ │ └── useUserPreferences.ts ← User preferences store
411
432
  ├── components/
412
433
  │ ├── ThemeConsumer.vue ← Successfully injects 'theme'
413
434
  │ ├── MissingProviderConsumer.vue ← Injects 'cartContext' (no provider — red node)
414
435
  │ ├── LeakyComponent.vue ← Mounts useLeakyPoller
436
+ │ ├── LeakyCartMonitor.vue ← Mounts useLeakyCartAudit
415
437
  │ ├── HeavyList.vue ← Re-renders on every shuffle (heatmap demo)
416
438
  │ ├── PriceDisplay.vue ← Leaf component with high render count
439
+ │ ├── CartDrawer.vue ← Cart sidebar
440
+ │ ├── CartItem.vue ← Individual cart item row
441
+ │ ├── DataTable.vue ← Generic sortable table
442
+ │ ├── SettingsPanel.vue ← Settings form panel
443
+ │ ├── StatsCard.vue ← Stat metric card
417
444
  │ └── transitions/
418
445
  │ ├── FadeBox.vue ← Healthy enter/leave transition
419
446
  │ ├── BrokenTransition.vue ← Missing CSS classes (enter fires but stays in entering)
420
- └── CancelledTransition.vue ← Rapid toggle triggers enter-cancelled / leave-cancelled
447
+ ├── CancelledTransition.vue ← Rapid toggle triggers enter-cancelled / leave-cancelled
448
+ │ └── InterruptedTransition.vue ← Back-to-back transitions trigger interrupted phase
421
449
  └── server/api/
422
450
  └── product.ts ← Mock API endpoint
423
451