nuxt-devtools-observatory 0.1.30 → 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.
Files changed (51) hide show
  1. package/README.md +117 -30
  2. package/client/.env.example +2 -1
  3. package/client/dist/assets/index-5Wl1XYRH.js +17 -0
  4. package/client/dist/assets/index-DT_QUiIh.css +1 -0
  5. package/client/dist/index.html +2 -2
  6. package/client/src/App.vue +4 -0
  7. package/client/src/components/Flamegraph.vue +442 -0
  8. package/client/src/components/SpanInspector.vue +446 -0
  9. package/client/src/components/TraceFilter.vue +342 -0
  10. package/client/src/components/WaterfallView.vue +443 -0
  11. package/client/src/composables/composable-search.ts +124 -0
  12. package/client/src/composables/trace-render-aggregation.ts +254 -0
  13. package/client/src/composables/useExportImport.ts +63 -0
  14. package/client/src/composables/useTraceFilter.ts +160 -0
  15. package/client/src/stores/observatory.ts +13 -1
  16. package/client/src/views/ComposableTracker.vue +65 -30
  17. package/client/src/views/RenderHeatmap.vue +63 -1
  18. package/client/src/views/TraceViewer.vue +1212 -0
  19. package/client/src/views/TransitionTimeline.vue +1 -6
  20. package/dist/module.d.mts +5 -0
  21. package/dist/module.json +1 -1
  22. package/dist/module.mjs +31 -45
  23. package/dist/runtime/composables/composable-registry.d.ts +19 -0
  24. package/dist/runtime/composables/composable-registry.js +63 -5
  25. package/dist/runtime/composables/render-registry.js +74 -110
  26. package/dist/runtime/composables/transition-registry.js +103 -28
  27. package/dist/runtime/instrumentation/asyncData.d.ts +9 -0
  28. package/dist/runtime/instrumentation/asyncData.js +49 -0
  29. package/dist/runtime/instrumentation/component.d.ts +2 -0
  30. package/dist/runtime/instrumentation/component.js +126 -0
  31. package/dist/runtime/instrumentation/fetch.d.ts +2 -0
  32. package/dist/runtime/instrumentation/fetch.js +89 -0
  33. package/dist/runtime/instrumentation/route.d.ts +6 -0
  34. package/dist/runtime/instrumentation/route.js +41 -0
  35. package/dist/runtime/nitro/fetch-capture.d.ts +1 -2
  36. package/dist/runtime/nitro/fetch-capture.js +85 -7
  37. package/dist/runtime/nitro/ssr-trace-store.d.ts +85 -0
  38. package/dist/runtime/nitro/ssr-trace-store.js +84 -0
  39. package/dist/runtime/plugin.js +82 -2
  40. package/dist/runtime/tracing/context.d.ts +9 -0
  41. package/dist/runtime/tracing/context.js +15 -0
  42. package/dist/runtime/tracing/trace.d.ts +25 -0
  43. package/dist/runtime/tracing/trace.js +0 -0
  44. package/dist/runtime/tracing/traceStore.d.ts +39 -0
  45. package/dist/runtime/tracing/traceStore.js +101 -0
  46. package/dist/runtime/tracing/tracing.d.ts +27 -0
  47. package/dist/runtime/tracing/tracing.js +48 -0
  48. package/package.json +5 -1
  49. package/client/.env +0 -16
  50. package/client/dist/assets/index-BCaKoHBH.js +0 -17
  51. package/client/dist/assets/index-BmGW_M3W.css +0 -1
package/README.md CHANGED
@@ -2,13 +2,17 @@
2
2
 
3
3
  # Nuxt DevTools Observatory
4
4
 
5
- Nuxt DevTools module providing five missing observability features:
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
+
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
8
11
  - **provide/inject Graph** — interactive tree showing the full injection topology, value inspection, scope labels, shadow detection, and missing-provider warnings
9
12
  - **Composable Tracker** — live view of active composables, reactive state, change history, leak detection, inline value editing, and reverse lookup
10
13
  - **Render Heatmap** — component tree colour-coded by render frequency and duration, with per-render timeline, route filtering, and persistent-component accuracy fixes
11
14
  - **Transition Tracker** — live timeline of every `<Transition>` lifecycle event with phase, duration, and cancellation state
15
+ - **Trace Viewer** — per-route span traces capturing component mount order, real render durations, fetch timing, composable setup, and navigation events in a unified Flamegraph and Waterfall view
12
16
 
13
17
  ## Documentation website
14
18
 
@@ -40,7 +44,8 @@ Options set in `nuxt.config.ts` take precedence over environment variables.
40
44
  - `provideInjectGraph` (boolean) — Enable provide/inject graph
41
45
  - `composableTracker` (boolean) — Enable composable tracker
42
46
  - `renderHeatmap` (boolean) — Enable render heatmap
43
- - `transitionTracker` (boolean) — Enable transition tracker
47
+ - `transitionTracker` (boolean) — Enable transition tracker (set via `OBSERVATORY_TRANSITION_TRACKER`)
48
+ - `traceViewer` (boolean) — Enable trace viewer tab with per-route Flamegraph and Waterfall (set via `OBSERVATORY_TRACE_VIEWER`)
44
49
  - `composableNavigationMode` (string, 'route' | 'session') — Composable tracker mode: 'route' clears entries on page navigation (default), 'session' persists entries across navigation for historical inspection (`OBSERVATORY_COMPOSABLE_NAVIGATION_MODE`)
45
50
  - `heatmapThresholdCount` (number) — Highlight components with N+ renders in heatmap
46
51
  - `heatmapThresholdTime` (number) — Highlight components with render time above this (ms)
@@ -68,6 +73,7 @@ export default defineNuxtConfig({
68
73
  composableTracker: true, // Enable composable tracker
69
74
  renderHeatmap: true, // Enable render heatmap
70
75
  transitionTracker: true, // Enable transition tracker
76
+ traceViewer: true, // Enable trace viewer
71
77
  composableNavigationMode: 'route', // 'route' clears entries on navigation (default), 'session' persists across navigation
72
78
  heatmapThresholdCount: 5, // Highlight components with 5+ renders
73
79
  heatmapThresholdTime: 1600, // Highlight components with render time above this (ms)
@@ -85,7 +91,7 @@ export default defineNuxtConfig({
85
91
  })
86
92
  ```
87
93
 
88
- Open the Nuxt DevTools panel — five new tabs will appear.
94
+ Open the Nuxt DevTools panel — six new tabs will appear.
89
95
 
90
96
  The DevTools client SPA is served same-origin via the Nuxt dev server at `/__observatory/`.
91
97
 
@@ -159,27 +165,22 @@ wraps them with a tracking shim (`__trackComposable`) that:
159
165
  The panel provides:
160
166
 
161
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
162
- - **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
163
169
  - **Recency-first ordering** — newest entries appear first, with layout-level composables pinned to the top (layout composables persist across page navigation)
164
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
165
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
166
172
  - **Global state badges** — keys shared across instances are highlighted in amber with a `global` badge and an explanatory banner when expanded
167
173
  - **Change history** — a scrollable log of the last 50 value mutations with key, new value, and relative timestamp
168
174
  - **Lifecycle summary** — shows whether `onMounted`/`onUnmounted` were registered and whether watchers and intervals were properly cleaned up
169
- - **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
170
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
171
177
  - **Jump to editor** — an `open ↗` button in the context section opens the composable's source file in the configured editor
172
178
 
173
- **Known gaps:**
174
-
175
- - Reverse lookup matches by key name only, not by object identity
176
- - Search does not look inside nested `reactive` object properties
177
-
178
179
  ### Render Heatmap
179
180
 
180
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)
181
182
 
182
- Uses Vue's built-in `renderTriggered` mixin hook and `app.config.performance = true`.
183
+ Uses Vue lifecycle instrumentation with `app.config.performance = true`.
183
184
  Accurate duration is measured by bracketing each `beforeMount`/`mounted` and
184
185
  `beforeUpdate`/`updated` cycle with `performance.now()` timestamps.
185
186
  Component bounding boxes are captured via `$el.getBoundingClientRect()` for the DOM
@@ -191,8 +192,7 @@ last). Every mount and update cycle appends an event recording:
191
192
  - `kind` — `mount` or `update`
192
193
  - `t` — `performance.now()` timestamp
193
194
  - `durationMs` — measured render duration
194
- - `triggerKey` — the reactive dep that caused the update (when `renderTriggered` fired
195
- before `updated`), formatted as `type: key`
195
+ - `triggerKey` — optional reactive dep label for the update (when available)
196
196
  - `route` — the route path at the time of the render
197
197
 
198
198
  A `route` field on each entry records which route the component was first seen on.
@@ -221,12 +221,70 @@ The panel provides:
221
221
  panel's identity section has an `open ↗` button; both call Vite's built-in
222
222
  `/__open-in-editor` endpoint to open the component's source file in the configured
223
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
224
229
 
225
230
  **Known gaps:**
226
231
 
227
232
  - The route filter shows components active on a route but cannot hide persistent
228
233
  components (they appear on every route by definition)
229
234
 
235
+ ### Trace Viewer
236
+
237
+ [![Trace Viewer](https://github.com/victorlmneves/nuxt-devtools-observatory/blob/main/docs/screenshots/trace-viewer.png)](https://github.com/victorlmneves/nuxt-devtools-observatory/blob/main/docs/screenshots/trace-viewer.png)
238
+
239
+ The Trace Viewer automatically collects per-route traces that span the full lifecycle of
240
+ each page visit. Every significant event is recorded as a typed span and grouped into a
241
+ single `TraceEntry` per navigation, so you can see everything that happened — in order —
242
+ after a route change.
243
+
244
+ **Span types collected:**
245
+
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 |
255
+
256
+ **Render span tracking:**
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).
258
+
259
+ **Trace anchoring:**
260
+ Each `TraceEntry` is anchored to the `startTime` of its first span, so bar positions in the timeline are always relative to the first event in the trace rather than the time the trace object was created.
261
+
262
+ The panel provides:
263
+
264
+ - **Trace list** — every captured route visit shown with name, total duration, and span count; click to open
265
+ - **Flamegraph tab** — collapsible span tree with horizontal duration bars, depth-based indentation, parent–child nesting, and a selected-span highlight (purple tint)
266
+ - **Waterfall tab** — spans grouped by type with a shared horizontal timeline, group headers, and status badges
267
+ - **Span inspector** — clicking any span opens a detail panel showing type, status, duration, start offset, and all metadata fields
268
+ - **Filter panel** — filter by span type and free-text search across span names and metadata
269
+ - **Duration labels** — spans narrower than 5 % of the timeline render their label outside the bar for readability
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
279
+
280
+ **What it tells you:**
281
+
282
+ - Mount order and render cost of every component on the route
283
+ - Whether render time is dominated by the component `setup()` / lifecycle hooks or by actual DOM patching
284
+ - Which `useFetch` calls are in the critical path and how long each took
285
+ - Whether composable setup is adding meaningful latency
286
+ - Which components are slow to mount and might benefit from `<Suspense>` or lazy loading
287
+
230
288
  ### Transition Tracker
231
289
 
232
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)
@@ -272,10 +330,7 @@ const result = useMyComposable()
272
330
 
273
331
  ## Roadmap
274
332
 
275
- ### Composable Tracker
276
-
277
- - [ ] Reverse lookup by object identity rather than key name only
278
- - [ ] Deep search inside nested `reactive` object properties
333
+ - [x] Dedicated cross-trace comparison UI polish (sorting, stronger visual deltas, and quick filtering)
279
334
 
280
335
  ## Development
281
336
 
@@ -315,16 +370,26 @@ src/
315
370
  │ ├── provide-inject-transform.ts ← AST wraps provide/inject
316
371
  │ ├── composable-transform.ts ← AST wraps useX() composables
317
372
  │ └── transition-transform.ts ← Virtual vue proxy — overrides Transition export
318
- ├── runtime/
319
- ├── plugin.ts ← Client runtime bootstrap + RPC/Vite WS bridge
320
- │ └── composables/
321
- ├── fetch-registry.ts ← Fetch tracking store + __devFetch shim
322
- ├── provide-inject-registry.ts ← Injection tracking + __devProvide/__devInject
323
- ├── composable-registry.ts ← Composable tracking + __trackComposable + leak detection
324
- ├── render-registry.ts ← Render performance data via PerformanceObserver
325
- └── transition-registry.ts ← Transition lifecycle store
326
- └── nitro/
327
- └── 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)
328
393
 
329
394
  client/
330
395
  ├── index.html
@@ -335,30 +400,52 @@ client/
335
400
  ├── main.ts
336
401
  ├── style.css ← Design system
337
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
338
409
  ├── stores/
339
410
  └── views/
340
411
  ├── FetchDashboard.vue ← useFetch tab UI
341
412
  ├── ProvideInjectGraph.vue ← provide/inject tab UI
342
413
  ├── ComposableTracker.vue ← Composable tab UI
414
+ ├── ComponentBlock.vue ← Shared component detail block
415
+ ├── ValueInspector.vue ← Inline JSON value inspector
343
416
  ├── RenderHeatmap.vue ← Heatmap tab UI
344
- └── TransitionTimeline.vue ← Transition tracker tab UI
417
+ ├── TransitionTimeline.vue ← Transition tracker tab UI
418
+ └── TraceViewer.vue ← Trace viewer tab UI (Overview + Flamegraph + Waterfall + Inspector + cross-trace comparison)
345
419
 
346
420
  playground/
347
421
  ├── app.vue ← Demo app shell used during local development
348
422
  ├── nuxt.config.ts
349
423
  ├── composables/
350
424
  │ ├── useCounter.ts ← Clean composable (properly cleaned up)
351
- └── 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
352
432
  ├── components/
353
433
  │ ├── ThemeConsumer.vue ← Successfully injects 'theme'
354
434
  │ ├── MissingProviderConsumer.vue ← Injects 'cartContext' (no provider — red node)
355
435
  │ ├── LeakyComponent.vue ← Mounts useLeakyPoller
436
+ │ ├── LeakyCartMonitor.vue ← Mounts useLeakyCartAudit
356
437
  │ ├── HeavyList.vue ← Re-renders on every shuffle (heatmap demo)
357
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
358
444
  │ └── transitions/
359
445
  │ ├── FadeBox.vue ← Healthy enter/leave transition
360
446
  │ ├── BrokenTransition.vue ← Missing CSS classes (enter fires but stays in entering)
361
- └── 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
362
449
  └── server/api/
363
450
  └── product.ts ← Mock API endpoint
364
451
 
@@ -1,9 +1,10 @@
1
- # Observatory registry/configurable limits
1
+ # VITE_Observatory registry/configurable limits
2
2
  VITE_OBSERVATORY_FETCH_DASHBOARD=true
3
3
  VITE_OBSERVATORY_PROVIDE_INJECT_GRAPH=true
4
4
  VITE_OBSERVATORY_COMPOSABLE_TRACKER=true
5
5
  VITE_OBSERVATORY_RENDER_HEATMAP=true
6
6
  VITE_OBSERVATORY_TRANSITION_TRACKER=true
7
+ VITE_OBSERVATORY_TRACE_VIEWER=true
7
8
  VITE_OBSERVATORY_MAX_FETCH_ENTRIES=200
8
9
  VITE_OBSERVATORY_MAX_PAYLOAD_BYTES=10000
9
10
  VITE_OBSERVATORY_MAX_TRANSITIONS=500