nuxt-devtools-observatory 0.1.33 → 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.
@@ -4,6 +4,7 @@ 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'
@@ -13,6 +14,7 @@ const pathMap: Record<string, string> = {
13
14
  fetch: 'fetch',
14
15
  provide: 'provide',
15
16
  composables: 'composable',
17
+ pinia: 'pinia',
16
18
  heatmap: 'heatmap',
17
19
  transitions: 'transitions',
18
20
  traces: 'traces',
@@ -32,6 +34,7 @@ const tabs = computed(() => {
32
34
  f.fetchDashboard && { id: 'fetch', label: 'useFetch', icon: '⬡' },
33
35
  f.provideInjectGraph && { id: 'provide', label: 'provide/inject', icon: '⬡' },
34
36
  f.composableTracker && { id: 'composable', label: 'Composables', icon: '⬡' },
37
+ f.piniaTracker && { id: 'pinia', label: 'Pinia', icon: '⬡' },
35
38
  f.renderHeatmap && { id: 'heatmap', label: 'Heatmap', icon: '⬡' },
36
39
  f.transitionTracker && { id: 'transitions', label: 'Transitions', icon: '⬡' },
37
40
  f.traceViewer && { id: 'traces', label: 'Traces', icon: '⬡' },
@@ -53,6 +56,7 @@ const tabs = computed(() => {
53
56
  <FetchDashboard v-if="activeTab === 'fetch'" />
54
57
  <ProvideInjectGraph v-else-if="activeTab === 'provide'" />
55
58
  <ComposableTracker v-else-if="activeTab === 'composable'" />
59
+ <PiniaStoreTracker v-else-if="activeTab === 'pinia'" />
56
60
  <RenderHeatmap v-else-if="activeTab === 'heatmap'" />
57
61
  <TransitionTimeline v-else-if="activeTab === 'transitions'" />
58
62
  <TraceViewer v-else-if="activeTab === 'traces'" />
@@ -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,
@@ -0,0 +1,343 @@
1
+ <script setup lang="ts">
2
+ // No store is accessed or created here, so Pinia Tracker will show 'no store available' by default.
3
+ import { computed, ref } from 'vue'
4
+ import { useObservatoryData, clearPiniaStores /* editPiniaState */ } from '@observatory-client/stores/observatory'
5
+ import type { PiniaMutationEvent, PiniaStoreEntry } from '@observatory/types/snapshot'
6
+
7
+ const { piniaStores, connected } = useObservatoryData()
8
+
9
+ const selectedStoreId = ref<string | null>(null)
10
+ const selectedEventId = ref<string | null>(null)
11
+ // Disabled until we can get it working properly. See comments in the template and applyEdit function for details.
12
+ // const editPath = ref('')
13
+ // const editValue = ref('')
14
+ const editError = ref('')
15
+
16
+ const stores = computed(() => [...piniaStores.value].sort((a, b) => a.id.localeCompare(b.id)))
17
+
18
+ const selectedStore = computed<PiniaStoreEntry | null>(() => {
19
+ if (!selectedStoreId.value) {
20
+ return stores.value[0] ?? null
21
+ }
22
+
23
+ return stores.value.find((item) => item.id === selectedStoreId.value) ?? null
24
+ })
25
+
26
+ const timeline = computed<PiniaMutationEvent[]>(() => {
27
+ return selectedStore.value ? [...selectedStore.value.timeline].slice().reverse() : []
28
+ })
29
+
30
+ const selectedEvent = computed<PiniaMutationEvent | null>(() => {
31
+ if (!selectedEventId.value) {
32
+ return timeline.value[0] ?? null
33
+ }
34
+
35
+ return timeline.value.find((event) => event.id === selectedEventId.value) ?? null
36
+ })
37
+
38
+ function selectStore(id: string) {
39
+ selectedStoreId.value = id
40
+ selectedEventId.value = null
41
+ editError.value = ''
42
+ }
43
+
44
+ function pretty(value: unknown) {
45
+ try {
46
+ return JSON.stringify(value, null, 2)
47
+ } catch {
48
+ return String(value)
49
+ }
50
+ }
51
+
52
+ function renderDuration(event: PiniaMutationEvent) {
53
+ if (typeof event.durationMs !== 'number') {
54
+ return '-'
55
+ }
56
+
57
+ return `${event.durationMs.toFixed(2)}ms`
58
+ }
59
+
60
+ /* Edit functionality is temporarily disabled due to issues with state updates not being reflected in the UI. This is likely related to how the editPiniaState function applies changes and how the store's reactivity system detects those changes. Further investigation is needed to determine the root cause and implement a proper fix.
61
+ function applyEdit() {
62
+ const store = selectedStore.value
63
+
64
+ if (!store) {
65
+ editError.value = 'Select a store first.'
66
+
67
+ return
68
+ }
69
+
70
+ if (!editPath.value.trim()) {
71
+ editError.value = 'Path is required (for example: preferences.theme).'
72
+
73
+ return
74
+ }
75
+
76
+ let nextValue: unknown = editValue.value
77
+
78
+ if (editValue.value.trim()) {
79
+ try {
80
+ nextValue = JSON.parse(editValue.value)
81
+ } catch {
82
+ nextValue = editValue.value
83
+ }
84
+ }
85
+
86
+ editError.value = ''
87
+ editPiniaState(store.id, editPath.value.trim(), nextValue)
88
+ } */
89
+ </script>
90
+
91
+ <template>
92
+ <section class="pinia-tracker">
93
+ <header class="toolbar">
94
+ <h2>Pinia State and Mutations</h2>
95
+ <div class="toolbar-actions">
96
+ <span v-if="connected" class="status connected">Connected</span>
97
+ <span v-else class="status">Waiting for app</span>
98
+ <button class="danger" @click="clearPiniaStores">Clear timeline</button>
99
+ </div>
100
+ </header>
101
+
102
+ <div v-if="stores.length === 0" class="empty">No Pinia stores detected yet. Trigger store usage in the app.</div>
103
+
104
+ <div v-else class="content-grid">
105
+ <aside class="panel stores">
106
+ <h3>Stores</h3>
107
+ <button
108
+ v-for="store in stores"
109
+ :key="store.id"
110
+ class="store-row"
111
+ :class="{ active: selectedStore?.id === store.id }"
112
+ @click="selectStore(store.id)"
113
+ >
114
+ <span>{{ store.name }}</span>
115
+ <small>{{ store.timeline.length }} events</small>
116
+ </button>
117
+ </aside>
118
+
119
+ <section class="panel timeline">
120
+ <h3>Timeline</h3>
121
+ <div class="list">
122
+ <button
123
+ v-for="event in timeline"
124
+ :key="event.id"
125
+ class="event-row"
126
+ :class="{ active: selectedEvent?.id === event.id }"
127
+ @click="selectedEventId = event.id"
128
+ >
129
+ <strong>{{ event.kind }}</strong>
130
+ <span>{{ event.name }}</span>
131
+ <small>{{ renderDuration(event) }}</small>
132
+ </button>
133
+ </div>
134
+ </section>
135
+
136
+ <section class="panel inspector">
137
+ <h3>Inspector</h3>
138
+
139
+ <div v-if="selectedStore" class="block">
140
+ <h4>Current state</h4>
141
+ <pre>{{ pretty(selectedStore.state) }}</pre>
142
+ </div>
143
+
144
+ <!-- Edit functionality is temporarily disabled due to issues with state updates not being reflected in the UI. This is likely related to how the editPiniaState function applies changes and how the store's reactivity system detects those changes. Further investigation is needed to determine the root cause and implement a proper fix.
145
+ <div v-if="selectedStore" class="block edit-box">
146
+ <h4>Edit state path</h4>
147
+ <input v-model="editPath" placeholder="preferences.theme" />
148
+ <textarea v-model="editValue" rows="4" placeholder='"dark" or {"enabled":true}' />
149
+ <div class="actions">
150
+ <button @click="applyEdit">Apply edit</button>
151
+ <small v-if="editError" class="error">{{ editError }}</small>
152
+ </div>
153
+ </div>
154
+ -->
155
+
156
+ <div v-if="selectedEvent" class="block">
157
+ <h4>Selected event</h4>
158
+ <p class="meta">
159
+ {{ selectedEvent.kind }} · {{ selectedEvent.name }} · {{ selectedEvent.status }} ·
160
+ {{ renderDuration(selectedEvent) }}
161
+ </p>
162
+ <h5>Diff</h5>
163
+ <ul class="diff-list">
164
+ <li v-for="item in selectedEvent.diff" :key="item.path">
165
+ <code>{{ item.path }}</code>
166
+ </li>
167
+ </ul>
168
+ <h5>Before</h5>
169
+ <pre>{{ pretty(selectedEvent.beforeState) }}</pre>
170
+ <h5>After</h5>
171
+ <pre>{{ pretty(selectedEvent.afterState) }}</pre>
172
+ </div>
173
+ </section>
174
+ </div>
175
+ </section>
176
+ </template>
177
+
178
+ <style scoped>
179
+ .pinia-tracker {
180
+ display: flex;
181
+ flex-direction: column;
182
+ height: 100%;
183
+ gap: 12px;
184
+ padding: 12px;
185
+ }
186
+
187
+ .toolbar {
188
+ display: flex;
189
+ justify-content: space-between;
190
+ align-items: center;
191
+ }
192
+
193
+ .toolbar h2 {
194
+ margin: 0;
195
+ font-size: 14px;
196
+ }
197
+
198
+ .toolbar-actions {
199
+ display: flex;
200
+ align-items: center;
201
+ gap: 8px;
202
+ }
203
+
204
+ .status {
205
+ color: var(--text3);
206
+ font-size: 12px;
207
+ }
208
+
209
+ .status.connected {
210
+ color: var(--green);
211
+ }
212
+
213
+ .content-grid {
214
+ display: grid;
215
+ grid-template-columns: 220px 280px 1fr;
216
+ gap: 10px;
217
+ min-height: 0;
218
+ flex: 1;
219
+ }
220
+
221
+ .panel {
222
+ border: 1px solid var(--border);
223
+ border-radius: 8px;
224
+ background: var(--bg2);
225
+ padding: 10px;
226
+ min-height: 0;
227
+ display: flex;
228
+ flex-direction: column;
229
+ gap: 8px;
230
+ }
231
+
232
+ .panel h3 {
233
+ margin: 0;
234
+ font-size: 12px;
235
+ color: var(--text2);
236
+ letter-spacing: 0.02em;
237
+ text-transform: uppercase;
238
+ }
239
+
240
+ .stores,
241
+ .timeline,
242
+ .inspector,
243
+ .list {
244
+ overflow: auto;
245
+ }
246
+
247
+ .store-row,
248
+ .event-row {
249
+ width: 100%;
250
+ border: 1px solid var(--border);
251
+ border-radius: 6px;
252
+ background: var(--bg3);
253
+ color: var(--text);
254
+ text-align: left;
255
+ padding: 8px;
256
+ display: flex;
257
+ flex-direction: column;
258
+ gap: 2px;
259
+ cursor: pointer;
260
+ }
261
+
262
+ .store-row.active,
263
+ .event-row.active {
264
+ border-color: var(--purple);
265
+ }
266
+
267
+ .store-row small,
268
+ .event-row small {
269
+ color: var(--text3);
270
+ }
271
+
272
+ .block {
273
+ border-top: 1px solid var(--border);
274
+ padding-top: 8px;
275
+ }
276
+
277
+ .block h4,
278
+ .block h5 {
279
+ margin: 0 0 6px;
280
+ font-size: 12px;
281
+ }
282
+
283
+ .meta {
284
+ margin: 0 0 6px;
285
+ color: var(--text2);
286
+ font-size: 12px;
287
+ }
288
+
289
+ pre {
290
+ margin: 0;
291
+ background: var(--bg3);
292
+ border: 1px solid var(--border);
293
+ border-radius: 6px;
294
+ padding: 8px;
295
+ white-space: pre-wrap;
296
+ word-break: break-word;
297
+ font-size: 11px;
298
+ max-height: 200px;
299
+ overflow: auto;
300
+ }
301
+
302
+ .edit-box input,
303
+ .edit-box textarea {
304
+ width: 100%;
305
+ background: var(--bg3);
306
+ border: 1px solid var(--border);
307
+ border-radius: 6px;
308
+ color: var(--text);
309
+ padding: 6px;
310
+ font-size: 12px;
311
+ }
312
+
313
+ .actions {
314
+ display: flex;
315
+ align-items: center;
316
+ gap: 8px;
317
+ }
318
+
319
+ .error {
320
+ color: var(--red);
321
+ }
322
+
323
+ .empty {
324
+ padding: 24px;
325
+ border: 1px dashed var(--border);
326
+ border-radius: 8px;
327
+ color: var(--text2);
328
+ }
329
+
330
+ .diff-list {
331
+ margin: 0;
332
+ padding-left: 16px;
333
+ }
334
+
335
+ .danger {
336
+ border: 1px solid var(--border);
337
+ border-radius: 6px;
338
+ background: var(--bg2);
339
+ color: var(--text);
340
+ padding: 6px 10px;
341
+ cursor: pointer;
342
+ }
343
+ </style>
@@ -380,6 +380,22 @@ const visibleNodes = computed<TreeNodeData[]>(() => {
380
380
 
381
381
  const visibleLeafCountById = computed(() => buildLeafCountMap(visibleNodes.value))
382
382
 
383
+ function findNodeById(roots: TreeNodeData[], id: string): TreeNodeData | null {
384
+ const stack = [...roots]
385
+
386
+ while (stack.length) {
387
+ const node = stack.pop()!
388
+
389
+ if (node.id === id) {
390
+ return node
391
+ }
392
+
393
+ stack.push(...node.children)
394
+ }
395
+
396
+ return null
397
+ }
398
+
383
399
  watch([visibleNodes, selectedNode], ([currentNodes, currentSelected]) => {
384
400
  if (!currentSelected) {
385
401
  return
@@ -396,6 +412,15 @@ watch([visibleNodes, selectedNode], ([currentNodes, currentSelected]) => {
396
412
 
397
413
  if (!ids.has(currentSelected.id)) {
398
414
  selectedNode.value = null
415
+
416
+ return
417
+ }
418
+
419
+ // Keep details reactive: remap to the latest node instance after snapshots refresh.
420
+ const latest = findNodeById(currentNodes, currentSelected.id)
421
+
422
+ if (latest && latest !== currentSelected) {
423
+ selectedNode.value = latest
399
424
  }
400
425
  })
401
426
 
package/dist/module.d.mts CHANGED
@@ -39,6 +39,11 @@ interface ModuleOptions {
39
39
  * @default 300
40
40
  */
41
41
  maxComposableEntries?: number;
42
+ /**
43
+ * Maximum number of Pinia timeline events to keep per store
44
+ * @default 100
45
+ */
46
+ maxPiniaTimeline?: number;
42
47
  /**
43
48
  * Maximum number of render timeline events per entry
44
49
  * @default 100
@@ -66,6 +71,11 @@ interface ModuleOptions {
66
71
  * @default true
67
72
  */
68
73
  composableTracker?: boolean;
74
+ /**
75
+ * Enable the Pinia state tracker tab
76
+ * @default true
77
+ */
78
+ piniaTracker?: boolean;
69
79
  /**
70
80
  * Enable the render heatmap tab
71
81
  * @default true
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.33",
7
+ "version": "0.1.34",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -518,6 +518,7 @@ const defaults = {
518
518
  fetchDashboard: process.env.OBSERVATORY_FETCH_DASHBOARD === "true",
519
519
  provideInjectGraph: process.env.OBSERVATORY_PROVIDE_INJECT_GRAPH === "true",
520
520
  composableTracker: process.env.OBSERVATORY_COMPOSABLE_TRACKER === "true",
521
+ piniaTracker: process.env.OBSERVATORY_PINIA_TRACKER === "true",
521
522
  renderHeatmap: process.env.OBSERVATORY_RENDER_HEATMAP === "true",
522
523
  transitionTracker: process.env.OBSERVATORY_TRANSITION_TRACKER === "true",
523
524
  traceViewer: process.env.OBSERVATORY_TRACE_VIEWER === "true",
@@ -529,6 +530,7 @@ const defaults = {
529
530
  maxTransitions: process.env.OBSERVATORY_MAX_TRANSITIONS ? Number(process.env.OBSERVATORY_MAX_TRANSITIONS) : 500,
530
531
  maxComposableHistory: process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY) : 50,
531
532
  maxComposableEntries: process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES) : 300,
533
+ maxPiniaTimeline: process.env.OBSERVATORY_MAX_PINIA_TIMELINE ? Number(process.env.OBSERVATORY_MAX_PINIA_TIMELINE) : 100,
532
534
  maxRenderTimeline: process.env.OBSERVATORY_MAX_RENDER_TIMELINE ? Number(process.env.OBSERVATORY_MAX_RENDER_TIMELINE) : 100,
533
535
  composableNavigationMode: process.env.OBSERVATORY_COMPOSABLE_NAVIGATION_MODE === "session" ? "session" : "route",
534
536
  heatmapHideInternals: process.env.OBSERVATORY_HEATMAP_HIDE_INTERNALS === "true",
@@ -557,6 +559,7 @@ const module$1 = defineNuxtModule({
557
559
  fetchDashboard: options.fetchDashboard ?? (process.env.OBSERVATORY_FETCH_DASHBOARD ? process.env.OBSERVATORY_FETCH_DASHBOARD === "true" : true),
558
560
  provideInjectGraph: options.provideInjectGraph ?? (process.env.OBSERVATORY_PROVIDE_INJECT_GRAPH ? process.env.OBSERVATORY_PROVIDE_INJECT_GRAPH === "true" : true),
559
561
  composableTracker: options.composableTracker ?? (process.env.OBSERVATORY_COMPOSABLE_TRACKER ? process.env.OBSERVATORY_COMPOSABLE_TRACKER === "true" : true),
562
+ piniaTracker: options.piniaTracker ?? (process.env.OBSERVATORY_PINIA_TRACKER ? process.env.OBSERVATORY_PINIA_TRACKER === "true" : true),
560
563
  renderHeatmap: options.renderHeatmap ?? (process.env.OBSERVATORY_RENDER_HEATMAP ? process.env.OBSERVATORY_RENDER_HEATMAP === "true" : true),
561
564
  transitionTracker: options.transitionTracker ?? (process.env.OBSERVATORY_TRANSITION_TRACKER ? process.env.OBSERVATORY_TRANSITION_TRACKER === "true" : true),
562
565
  traceViewer: options.traceViewer ?? (process.env.OBSERVATORY_TRACE_VIEWER ? process.env.OBSERVATORY_TRACE_VIEWER === "true" : true),
@@ -569,6 +572,7 @@ const module$1 = defineNuxtModule({
569
572
  maxTransitions: options.maxTransitions ?? (process.env.OBSERVATORY_MAX_TRANSITIONS ? Number(process.env.OBSERVATORY_MAX_TRANSITIONS) : 500),
570
573
  maxComposableHistory: options.maxComposableHistory ?? (process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY) : 50),
571
574
  maxComposableEntries: options.maxComposableEntries ?? (process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES) : 300),
575
+ maxPiniaTimeline: options.maxPiniaTimeline ?? (process.env.OBSERVATORY_MAX_PINIA_TIMELINE ? Number(process.env.OBSERVATORY_MAX_PINIA_TIMELINE) : 100),
572
576
  maxRenderTimeline: options.maxRenderTimeline ?? (process.env.OBSERVATORY_MAX_RENDER_TIMELINE ? Number(process.env.OBSERVATORY_MAX_RENDER_TIMELINE) : 100),
573
577
  composableNavigationMode: options.composableNavigationMode ?? (process.env.OBSERVATORY_COMPOSABLE_NAVIGATION_MODE === "session" ? "session" : "route"),
574
578
  debugRpc: options.debugRpc ?? (process.env.OBSERVATORY_DEBUG_RPC ? process.env.OBSERVATORY_DEBUG_RPC === "true" : false)
@@ -603,7 +607,7 @@ const module$1 = defineNuxtModule({
603
607
  if (resolved.transitionTracker) {
604
608
  addVitePlugin(transitionTrackerPlugin(), vitePluginScope);
605
609
  }
606
- if (resolved.fetchDashboard || resolved.provideInjectGraph || resolved.composableTracker || resolved.renderHeatmap || resolved.transitionTracker) {
610
+ if (resolved.fetchDashboard || resolved.provideInjectGraph || resolved.composableTracker || resolved.piniaTracker || resolved.renderHeatmap || resolved.transitionTracker) {
607
611
  addPlugin(resolver.resolve("./runtime/plugin"));
608
612
  }
609
613
  if (resolved.fetchDashboard || resolved.traceViewer && resolved.instrumentServer) {
@@ -620,6 +624,7 @@ const module$1 = defineNuxtModule({
620
624
  fetch: [],
621
625
  provideInject: { provides: [], injects: [] },
622
626
  composables: [],
627
+ piniaStores: [],
623
628
  renders: [],
624
629
  transitions: [],
625
630
  traces: [],
@@ -627,6 +632,7 @@ const module$1 = defineNuxtModule({
627
632
  fetchDashboard: !!resolved.fetchDashboard,
628
633
  provideInjectGraph: !!resolved.provideInjectGraph,
629
634
  composableTracker: !!resolved.composableTracker,
635
+ piniaTracker: !!resolved.piniaTracker,
630
636
  composableNavigationMode: resolved.composableNavigationMode,
631
637
  fetchPageSize: resolved.fetchPageSize,
632
638
  renderHeatmap: !!resolved.renderHeatmap,
@@ -655,6 +661,7 @@ const module$1 = defineNuxtModule({
655
661
  debugLog("received host snapshot", {
656
662
  fetch: Array.isArray(snapshot.fetch) ? snapshot.fetch.length : 0,
657
663
  composables: Array.isArray(snapshot.composables) ? snapshot.composables.length : 0,
664
+ piniaStores: Array.isArray(snapshot.piniaStores) ? snapshot.piniaStores.length : 0,
658
665
  renders: Array.isArray(snapshot.renders) ? snapshot.renders.length : 0,
659
666
  transitions: Array.isArray(snapshot.transitions) ? snapshot.transitions.length : 0
660
667
  });
@@ -680,6 +687,12 @@ const module$1 = defineNuxtModule({
680
687
  },
681
688
  async editComposableValue(id, key, value) {
682
689
  emitCommand({ cmd: "edit-composable", id, key, value });
690
+ },
691
+ async clearPiniaStores() {
692
+ emitCommand({ cmd: "clear-pinia" });
693
+ },
694
+ async editPiniaState(storeId, path, value) {
695
+ emitCommand({ cmd: "edit-pinia", storeId, path, value });
683
696
  }
684
697
  },
685
698
  nuxt
@@ -687,7 +700,7 @@ const module$1 = defineNuxtModule({
687
700
  rpc.broadcast.onSnapshot.asEvent(latestSnapshot);
688
701
  }, nuxt);
689
702
  nuxt.hook("devtools:customTabs", (tabs) => {
690
- if (resolved.fetchDashboard || resolved.provideInjectGraph || resolved.composableTracker || resolved.renderHeatmap || resolved.transitionTracker) {
703
+ if (resolved.fetchDashboard || resolved.provideInjectGraph || resolved.composableTracker || resolved.piniaTracker || resolved.renderHeatmap || resolved.transitionTracker) {
691
704
  tabs.push({
692
705
  name: "observatory-trackers",
693
706
  title: "Observatory Trackers",
@@ -701,6 +714,7 @@ const module$1 = defineNuxtModule({
701
714
  fetchDashboard: resolved.fetchDashboard,
702
715
  provideInjectGraph: resolved.provideInjectGraph,
703
716
  composableTracker: resolved.composableTracker,
717
+ piniaTracker: resolved.piniaTracker,
704
718
  renderHeatmap: resolved.renderHeatmap,
705
719
  transitionTracker: resolved.transitionTracker,
706
720
  traceViewer: resolved.traceViewer,
@@ -710,6 +724,7 @@ const module$1 = defineNuxtModule({
710
724
  maxTransitions: resolved.maxTransitions,
711
725
  maxComposableHistory: resolved.maxComposableHistory,
712
726
  maxComposableEntries: resolved.maxComposableEntries,
727
+ maxPiniaTimeline: resolved.maxPiniaTimeline,
713
728
  maxRenderTimeline: resolved.maxRenderTimeline,
714
729
  composableNavigationMode: resolved.composableNavigationMode,
715
730
  heatmapHideInternals: resolved.heatmapHideInternals,
@@ -0,0 +1,44 @@
1
+ import type { PiniaHydrationEvent, PiniaStateDiff, PiniaStoreDependency } from '../../types/snapshot.js';
2
+ export declare function setupPiniaStoreRegistry(options: {
3
+ pinia?: unknown;
4
+ nuxtPayload?: unknown;
5
+ maxTimeline?: number;
6
+ stackProvider?: () => string[];
7
+ }): {
8
+ clear: () => void;
9
+ editState: (storeId: string, path: string, value: unknown) => void;
10
+ getAll: () => {
11
+ state: unknown;
12
+ dependencies: PiniaStoreDependency[];
13
+ hydrationTimeline: {
14
+ at: number;
15
+ source: "nuxt-payload" | "persistedstate" | "runtime" | "unknown";
16
+ details?: string;
17
+ }[];
18
+ timeline: {
19
+ beforeState: unknown;
20
+ afterState: unknown;
21
+ diff: PiniaStateDiff[];
22
+ id: string;
23
+ storeId: string;
24
+ storeName: string;
25
+ kind: "action" | "mutation";
26
+ name: string;
27
+ startTime: number;
28
+ endTime?: number;
29
+ durationMs?: number;
30
+ status: "active" | "ok" | "error";
31
+ callerStack?: string[];
32
+ payload?: unknown;
33
+ error?: string;
34
+ }[];
35
+ id: string;
36
+ name: string;
37
+ lastMutationAt?: number;
38
+ lastActionAt?: number;
39
+ hydration?: PiniaHydrationEvent;
40
+ }[];
41
+ getSnapshot: () => string;
42
+ onChange: (cb: () => void) => () => void;
43
+ teardown: () => void;
44
+ };