@buoy-gg/events 2.1.2 → 2.1.3

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 (44) hide show
  1. package/lib/commonjs/components/EventsCopySettingsView.js +39 -8
  2. package/lib/commonjs/components/UnifiedEventDetail.js +32 -1
  3. package/lib/commonjs/components/UnifiedEventFilters.js +50 -21
  4. package/lib/commonjs/components/UnifiedEventItem.js +6 -1
  5. package/lib/commonjs/hooks/useUnifiedEvents.js +137 -28
  6. package/lib/commonjs/index.js +6 -0
  7. package/lib/commonjs/stores/unifiedEventStore.js +61 -16
  8. package/lib/commonjs/types/copySettings.js +29 -0
  9. package/lib/commonjs/utils/autoDiscoverEventSources.js +261 -39
  10. package/lib/commonjs/utils/badgeSelectionStorage.js +32 -0
  11. package/lib/commonjs/utils/eventExportFormatter.js +778 -1
  12. package/lib/module/components/EventsCopySettingsView.js +40 -9
  13. package/lib/module/components/UnifiedEventDetail.js +32 -1
  14. package/lib/module/components/UnifiedEventFilters.js +50 -21
  15. package/lib/module/components/UnifiedEventItem.js +6 -1
  16. package/lib/module/hooks/useUnifiedEvents.js +140 -31
  17. package/lib/module/index.js +4 -1
  18. package/lib/module/stores/unifiedEventStore.js +58 -16
  19. package/lib/module/types/copySettings.js +29 -0
  20. package/lib/module/utils/autoDiscoverEventSources.js +260 -39
  21. package/lib/module/utils/badgeSelectionStorage.js +30 -0
  22. package/lib/module/utils/eventExportFormatter.js +777 -1
  23. package/lib/typescript/components/UnifiedEventFilters.d.ts +3 -0
  24. package/lib/typescript/hooks/useUnifiedEvents.d.ts +2 -0
  25. package/lib/typescript/index.d.ts +1 -1
  26. package/lib/typescript/stores/unifiedEventStore.d.ts +18 -2
  27. package/lib/typescript/types/copySettings.d.ts +25 -1
  28. package/lib/typescript/types/index.d.ts +3 -1
  29. package/lib/typescript/utils/autoDiscoverEventSources.d.ts +17 -0
  30. package/lib/typescript/utils/badgeSelectionStorage.d.ts +9 -0
  31. package/lib/typescript/utils/eventExportFormatter.d.ts +4 -0
  32. package/package.json +3 -3
  33. package/src/components/EventsCopySettingsView.tsx +41 -5
  34. package/src/components/UnifiedEventDetail.tsx +28 -0
  35. package/src/components/UnifiedEventFilters.tsx +88 -21
  36. package/src/components/UnifiedEventItem.tsx +5 -0
  37. package/src/hooks/useUnifiedEvents.ts +153 -25
  38. package/src/index.tsx +4 -0
  39. package/src/stores/unifiedEventStore.ts +58 -12
  40. package/src/types/copySettings.ts +31 -1
  41. package/src/types/index.ts +4 -1
  42. package/src/utils/autoDiscoverEventSources.ts +268 -44
  43. package/src/utils/badgeSelectionStorage.ts +30 -0
  44. package/src/utils/eventExportFormatter.ts +797 -0
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { useState, useEffect, useCallback, useMemo, useRef } from "react";
9
- import { useFeatureGate } from "@buoy-gg/shared-ui";
9
+ import { useFeatureGate, subscribeToSubscriberCountChanges } from "@buoy-gg/shared-ui";
10
10
  import type { UnifiedEvent, EventSource, SourceInfo } from "../types";
11
11
  import {
12
12
  subscribe,
@@ -15,14 +15,24 @@ import {
15
15
  subscribeToNetwork,
16
16
  subscribeToReactQuery,
17
17
  subscribeToRoutes,
18
+ subscribeToRender,
19
+ unsubscribeFromStorage,
20
+ unsubscribeFromRedux,
21
+ unsubscribeFromNetwork,
22
+ unsubscribeFromReactQuery,
23
+ unsubscribeFromRoutes,
24
+ unsubscribeFromRender,
18
25
  unsubscribeAll,
19
26
  getSourceCounts,
20
27
  clearEvents as clearStoreEvents,
21
28
  getAvailableEventSources,
29
+ getSubscriberCounts,
22
30
  } from "../stores/unifiedEventStore";
23
31
  import {
24
32
  saveEnabledSources,
25
33
  loadEnabledSources,
34
+ saveCapturingState,
35
+ loadCapturingState,
26
36
  } from "../utils/badgeSelectionStorage";
27
37
  import { getSourceDisplayConfig } from "../utils/autoDiscoverEventSources";
28
38
 
@@ -39,6 +49,7 @@ const ALL_DISPLAY_SOURCES: EventSource[] = [
39
49
  "react-query-query",
40
50
  "react-query-mutation",
41
51
  "route",
52
+ "render",
42
53
  ];
43
54
 
44
55
  /**
@@ -53,6 +64,7 @@ const SOURCE_TO_EVENT_SOURCES: Record<EventSource, EventSource[]> = {
53
64
  "react-query-query": ["react-query", "react-query-query"],
54
65
  "react-query-mutation": ["react-query-mutation"],
55
66
  route: ["route"],
67
+ render: ["render"],
56
68
  };
57
69
 
58
70
  /**
@@ -67,6 +79,7 @@ const EVENT_SOURCE_TO_DISCOVERY_ID: Record<EventSource, string> = {
67
79
  "react-query-query": "react-query",
68
80
  "react-query-mutation": "react-query",
69
81
  route: "route-events",
82
+ render: "render",
70
83
  };
71
84
 
72
85
  export interface UseUnifiedEventsResult {
@@ -89,6 +102,8 @@ export interface UseUnifiedEventsResult {
89
102
  hiddenEventsCount: number;
90
103
  /** Whether user is on Pro plan */
91
104
  isPro: boolean;
105
+ /** Total number of subscribers across all event stores (for debugging) */
106
+ totalSubscriberCount: number;
92
107
  }
93
108
 
94
109
  export function useUnifiedEvents(): UseUnifiedEventsResult {
@@ -105,6 +120,14 @@ export function useUnifiedEvents(): UseUnifiedEventsResult {
105
120
  return getAvailableEventSources();
106
121
  }, []);
107
122
 
123
+ // Subscribe to subscriber count changes for instant UI updates (TanStack Query pattern)
124
+ const [subscriberCountVersion, setSubscriberCountVersion] = useState(0);
125
+ useEffect(() => {
126
+ return subscribeToSubscriberCountChanges(() => {
127
+ setSubscriberCountVersion((v) => v + 1);
128
+ });
129
+ }, []);
130
+
108
131
  // Get available display sources (only show sources for installed packages)
109
132
  const availableDisplaySources = useMemo(() => {
110
133
  return ALL_DISPLAY_SOURCES.filter((displaySource) => {
@@ -114,43 +137,56 @@ export function useUnifiedEvents(): UseUnifiedEventsResult {
114
137
  });
115
138
  }, [discoveredSources]);
116
139
 
117
- // Subscribe to store changes and start capturing on mount
140
+ // Subscribe to store changes (always subscribe to get events when they come)
118
141
  useEffect(() => {
119
- // Subscribe to store updates
120
142
  const unsubscribe = subscribe((newEvents) => {
121
143
  setEvents(newEvents);
122
144
  });
123
-
124
- // Start capturing from all available sources
125
- // These functions are now safe - they check if the source is available
126
- subscribeToStorage();
127
- subscribeToRedux();
128
- subscribeToNetwork();
129
- subscribeToReactQuery();
130
- subscribeToRoutes();
131
-
132
145
  return unsubscribe;
133
146
  }, []);
134
147
 
135
- // Restore saved badge selection state on mount
148
+ // Restore saved state on mount (badge selection + capturing state)
136
149
  useEffect(() => {
137
150
  const restoreState = async () => {
151
+ // Load badge selection
138
152
  const savedSources = await loadEnabledSources();
153
+ let sourcesToEnable: Set<EventSource>;
154
+
139
155
  if (savedSources && savedSources.length > 0) {
140
156
  // Filter to only include valid sources that still exist and are available
141
157
  const validSources = savedSources.filter(
142
158
  (s) => availableDisplaySources.includes(s)
143
159
  );
144
160
  if (validSources.length > 0) {
145
- setEnabledSources(new Set(validSources));
161
+ sourcesToEnable = new Set(validSources);
146
162
  } else {
147
163
  // If no saved sources are valid, enable all available sources
148
- setEnabledSources(new Set(availableDisplaySources));
164
+ sourcesToEnable = new Set(availableDisplaySources);
149
165
  }
150
166
  } else {
151
167
  // No saved state - enable all available sources
152
- setEnabledSources(new Set(availableDisplaySources));
168
+ sourcesToEnable = new Set(availableDisplaySources);
169
+ }
170
+
171
+ setEnabledSources(sourcesToEnable);
172
+
173
+ // Load capturing state (default to true if not saved)
174
+ const savedCapturing = await loadCapturingState();
175
+ const shouldCapture = savedCapturing !== null ? savedCapturing : true;
176
+ setIsCapturing(shouldCapture);
177
+
178
+ // Start capturing if enabled - only subscribe to enabled sources
179
+ if (shouldCapture) {
180
+ if (sourcesToEnable.has("storage-async")) subscribeToStorage();
181
+ if (sourcesToEnable.has("redux")) subscribeToRedux();
182
+ if (sourcesToEnable.has("network")) subscribeToNetwork();
183
+ if (sourcesToEnable.has("react-query-query") || sourcesToEnable.has("react-query-mutation")) {
184
+ subscribeToReactQuery();
185
+ }
186
+ if (sourcesToEnable.has("route")) subscribeToRoutes();
187
+ if (sourcesToEnable.has("render")) subscribeToRender();
153
188
  }
189
+
154
190
  isStateRestoredRef.current = true;
155
191
  };
156
192
  restoreState();
@@ -164,6 +200,14 @@ export function useUnifiedEvents(): UseUnifiedEventsResult {
164
200
  saveEnabledSources(Array.from(enabledSources));
165
201
  }, [enabledSources]);
166
202
 
203
+ // Persist capturing state whenever it changes (after initial restoration)
204
+ useEffect(() => {
205
+ if (!isStateRestoredRef.current) {
206
+ return;
207
+ }
208
+ saveCapturingState(isCapturing);
209
+ }, [isCapturing]);
210
+
167
211
  // Build set of all event sources that should be shown
168
212
  const allowedEventSources = useMemo(() => {
169
213
  const allowed = new Set<EventSource>();
@@ -203,17 +247,44 @@ export function useUnifiedEvents(): UseUnifiedEventsResult {
203
247
  // Get all available sources with counts, sorted: enabled first, disabled last
204
248
  const availableSources = useMemo((): SourceInfo[] => {
205
249
  const counts = getSourceCounts();
250
+ const subscriberCounts = getSubscriberCounts();
251
+
252
+ // Build a map from source ID to subscriber count
253
+ const subscriberCountBySourceId: Record<string, number> = {};
254
+ for (const source of subscriberCounts.sources) {
255
+ subscriberCountBySourceId[source.sourceId] = source.counts.total;
256
+ }
257
+
258
+ // Map display source to store source ID
259
+ const displaySourceToStoreId: Record<EventSource, string> = {
260
+ "storage-async": "storage",
261
+ "storage-mmkv": "storage",
262
+ redux: "redux",
263
+ network: "network",
264
+ "react-query": "react-query",
265
+ "react-query-query": "react-query",
266
+ "react-query-mutation": "react-query",
267
+ route: "route-events",
268
+ render: "render",
269
+ };
206
270
 
207
271
  const sources = availableDisplaySources.map((source) => {
208
272
  const eventSources = SOURCE_TO_EVENT_SOURCES[source] || [source];
209
273
  const totalCount = eventSources.reduce((sum, s) => sum + (counts[s] || 0), 0);
210
274
  const displayConfig = getSourceDisplayConfig(source);
275
+ const storeId = displaySourceToStoreId[source];
276
+ // Only set subscriberCount if the source has subscriber tracking
277
+ // (undefined means no tracking, vs 0 which means tracked but no subscribers)
278
+ const subscriberCount = storeId && storeId in subscriberCountBySourceId
279
+ ? subscriberCountBySourceId[storeId]
280
+ : undefined;
211
281
 
212
282
  return {
213
283
  source,
214
284
  ...displayConfig,
215
285
  count: totalCount,
216
286
  enabled: enabledSources.has(source),
287
+ subscriberCount,
217
288
  };
218
289
  });
219
290
 
@@ -223,19 +294,68 @@ export function useUnifiedEvents(): UseUnifiedEventsResult {
223
294
  if (!a.enabled && b.enabled) return 1;
224
295
  return 0;
225
296
  });
226
- }, [events, enabledSources, availableDisplaySources]);
297
+ }, [events, enabledSources, availableDisplaySources, subscriberCountVersion]);
227
298
 
228
299
  const toggleSource = useCallback((source: EventSource) => {
229
300
  setEnabledSources((prev) => {
230
301
  const next = new Set(prev);
231
- if (next.has(source)) {
302
+ const wasEnabled = next.has(source);
303
+
304
+ if (wasEnabled) {
232
305
  next.delete(source);
306
+ // Unsubscribe from the source
307
+ switch (source) {
308
+ case "storage-async":
309
+ unsubscribeFromStorage();
310
+ break;
311
+ case "redux":
312
+ unsubscribeFromRedux();
313
+ break;
314
+ case "network":
315
+ unsubscribeFromNetwork();
316
+ break;
317
+ case "react-query-query":
318
+ case "react-query-mutation":
319
+ unsubscribeFromReactQuery();
320
+ break;
321
+ case "route":
322
+ unsubscribeFromRoutes();
323
+ break;
324
+ case "render":
325
+ unsubscribeFromRender();
326
+ break;
327
+ }
233
328
  } else {
234
329
  next.add(source);
330
+ // Subscribe to the source (only if capturing is active)
331
+ if (isCapturing) {
332
+ switch (source) {
333
+ case "storage-async":
334
+ subscribeToStorage();
335
+ break;
336
+ case "redux":
337
+ subscribeToRedux();
338
+ break;
339
+ case "network":
340
+ subscribeToNetwork();
341
+ break;
342
+ case "react-query-query":
343
+ case "react-query-mutation":
344
+ subscribeToReactQuery();
345
+ break;
346
+ case "route":
347
+ subscribeToRoutes();
348
+ break;
349
+ case "render":
350
+ subscribeToRender();
351
+ break;
352
+ }
353
+ }
235
354
  }
355
+
236
356
  return next;
237
357
  });
238
- }, []);
358
+ }, [isCapturing]);
239
359
 
240
360
  const enableAllSources = useCallback(() => {
241
361
  setEnabledSources(new Set(availableDisplaySources));
@@ -246,13 +366,17 @@ export function useUnifiedEvents(): UseUnifiedEventsResult {
246
366
  }, []);
247
367
 
248
368
  const startCapturing = useCallback(() => {
249
- subscribeToStorage();
250
- subscribeToRedux();
251
- subscribeToNetwork();
252
- subscribeToReactQuery();
253
- subscribeToRoutes();
369
+ // Only subscribe to sources that are enabled
370
+ if (enabledSources.has("storage-async")) subscribeToStorage();
371
+ if (enabledSources.has("redux")) subscribeToRedux();
372
+ if (enabledSources.has("network")) subscribeToNetwork();
373
+ if (enabledSources.has("react-query-query") || enabledSources.has("react-query-mutation")) {
374
+ subscribeToReactQuery();
375
+ }
376
+ if (enabledSources.has("route")) subscribeToRoutes();
377
+ if (enabledSources.has("render")) subscribeToRender();
254
378
  setIsCapturing(true);
255
- }, []);
379
+ }, [enabledSources]);
256
380
 
257
381
  const stopCapturing = useCallback(() => {
258
382
  unsubscribeAll();
@@ -267,6 +391,9 @@ export function useUnifiedEvents(): UseUnifiedEventsResult {
267
391
  }
268
392
  }, [isCapturing, startCapturing, stopCapturing]);
269
393
 
394
+ // Get subscriber counts (recalculates on every render, but it's a cheap operation)
395
+ const totalSubscriberCount = getSubscriberCounts().totalSubscribers;
396
+
270
397
  return {
271
398
  events,
272
399
  filteredEvents,
@@ -284,5 +411,6 @@ export function useUnifiedEvents(): UseUnifiedEventsResult {
284
411
  discoveredSources,
285
412
  hiddenEventsCount,
286
413
  isPro,
414
+ totalSubscriberCount,
287
415
  };
288
416
  }
package/src/index.tsx CHANGED
@@ -81,6 +81,7 @@ export {
81
81
  generateMarkdownExport,
82
82
  generateJsonExport,
83
83
  generatePlaintextExport,
84
+ generateMermaidExport,
84
85
  estimateExportSize,
85
86
  getExportSummary,
86
87
  filterEvents,
@@ -110,3 +111,6 @@ export {
110
111
  // - subscribeToRoutes, unsubscribeFromRoutes (internal subscription)
111
112
  // - getEvents, getActiveSources, getSourceCounts (internal store operations)
112
113
  // - clearEvents, subscribe, getEventCount (internal store operations)
114
+
115
+ // Note: For subscriber count notifications, use @buoy-gg/shared-ui:
116
+ // import { notifySubscriberCountChange, subscribeToSubscriberCountChanges } from "@buoy-gg/shared-ui";
@@ -14,7 +14,9 @@ import type {
14
14
  } from "../types";
15
15
  import {
16
16
  getCachedDiscovery,
17
+ getAggregatedSubscriberCounts,
17
18
  type DiscoveredEventSource,
19
+ type AggregatedSubscriberCounts,
18
20
  } from "../utils/autoDiscoverEventSources";
19
21
 
20
22
  const MAX_EVENTS = 200;
@@ -63,18 +65,22 @@ class UnifiedEventStore {
63
65
  return; // Already subscribed
64
66
  }
65
67
 
66
- // Run setup if needed
67
- if (source.setup) {
68
- await source.setup();
69
- }
68
+ try {
69
+ // Run setup if needed
70
+ if (source.setup) {
71
+ await source.setup();
72
+ }
70
73
 
71
- // Subscribe and track the unsubscriber
72
- const unsubscribe = await source.subscribe((event) => {
73
- this.addEvent(event);
74
- });
74
+ // Subscribe and track the unsubscriber
75
+ const unsubscribe = await source.subscribe((event) => {
76
+ this.addEvent(event);
77
+ });
75
78
 
76
- if (unsubscribe) {
77
- this.sourceUnsubscribers.set(source.id, unsubscribe);
79
+ if (unsubscribe) {
80
+ this.sourceUnsubscribers.set(source.id, unsubscribe);
81
+ }
82
+ } catch {
83
+ // Silently fail - source may not be available
78
84
  }
79
85
  }
80
86
 
@@ -145,7 +151,8 @@ class UnifiedEventStore {
145
151
  unsubscribeFromNetwork(): void {
146
152
  this.unsubscribeFromSource("network");
147
153
  this.activeSources.delete("network");
148
- this.networkEventIdMap.clear();
154
+ // Don't clear networkEventIdMap - it's needed to deduplicate events
155
+ // when resubscribing while requests are still in flight
149
156
  }
150
157
 
151
158
  /**
@@ -188,6 +195,25 @@ class UnifiedEventStore {
188
195
  this.activeSources.delete("route");
189
196
  }
190
197
 
198
+ /**
199
+ * Subscribe to render events (if @buoy-gg/highlight-updates is installed)
200
+ */
201
+ subscribeToRender(): void {
202
+ const { sources } = getCachedDiscovery();
203
+ const renderSource = sources.find((s) => s.id === "render");
204
+ if (renderSource) {
205
+ this.subscribeToSource(renderSource);
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Unsubscribe from render events
211
+ */
212
+ unsubscribeFromRender(): void {
213
+ this.unsubscribeFromSource("render");
214
+ this.activeSources.delete("render");
215
+ }
216
+
191
217
  /**
192
218
  * Add an event to the store
193
219
  */
@@ -257,6 +283,7 @@ class UnifiedEventStore {
257
283
  "react-query-query": 0,
258
284
  "react-query-mutation": 0,
259
285
  route: 0,
286
+ render: 0,
260
287
  };
261
288
 
262
289
  for (const event of this.events) {
@@ -311,7 +338,7 @@ class UnifiedEventStore {
311
338
  */
312
339
  unsubscribeAll(): void {
313
340
  // Unsubscribe from all tracked sources
314
- for (const [sourceId, unsubscribe] of this.sourceUnsubscribers) {
341
+ for (const [, unsubscribe] of this.sourceUnsubscribers) {
315
342
  unsubscribe();
316
343
  }
317
344
  this.sourceUnsubscribers.clear();
@@ -339,6 +366,8 @@ class UnifiedEventStore {
339
366
  return this.sourceUnsubscribers.has("react-query");
340
367
  case "route":
341
368
  return this.sourceUnsubscribers.has("route-events");
369
+ case "render":
370
+ return this.sourceUnsubscribers.has("render");
342
371
  default:
343
372
  return false;
344
373
  }
@@ -357,8 +386,17 @@ class UnifiedEventStore {
357
386
  "react-query-query": this.sourceUnsubscribers.has("react-query"),
358
387
  "react-query-mutation": this.sourceUnsubscribers.has("react-query"),
359
388
  route: this.sourceUnsubscribers.has("route-events"),
389
+ render: this.sourceUnsubscribers.has("render"),
360
390
  };
361
391
  }
392
+
393
+ /**
394
+ * Get subscriber counts from all underlying event stores.
395
+ * Useful for debugging the event system.
396
+ */
397
+ getSubscriberCounts(): AggregatedSubscriberCounts {
398
+ return getAggregatedSubscriberCounts();
399
+ }
362
400
  }
363
401
 
364
402
  // Singleton instance
@@ -385,6 +423,10 @@ export const subscribeToRoutes = () =>
385
423
  unifiedEventStore.subscribeToRoutes();
386
424
  export const unsubscribeFromRoutes = () =>
387
425
  unifiedEventStore.unsubscribeFromRoutes();
426
+ export const subscribeToRender = () =>
427
+ unifiedEventStore.subscribeToRender();
428
+ export const unsubscribeFromRender = () =>
429
+ unifiedEventStore.unsubscribeFromRender();
388
430
  export const getEvents = (enabledSources?: Set<EventSource>) =>
389
431
  unifiedEventStore.getEvents(enabledSources);
390
432
  export const getActiveSources = () => unifiedEventStore.getActiveSources();
@@ -403,3 +445,7 @@ export const unsubscribeAll = () => unifiedEventStore.unsubscribeAll();
403
445
  export const getDiscoveredSources = () => unifiedEventStore.getDiscoveredSources();
404
446
  export const getAvailableEventSources = () => unifiedEventStore.getAvailableEventSources();
405
447
  export const subscribeToAll = () => unifiedEventStore.subscribeToAll();
448
+ export const getSubscriberCounts = () => unifiedEventStore.getSubscriberCounts();
449
+
450
+ // Re-export type for convenience
451
+ export type { AggregatedSubscriberCounts } from "../utils/autoDiscoverEventSources";
@@ -30,7 +30,7 @@ export interface EventsCopySettings {
30
30
  dataSizeThreshold: 1 | 5 | 10 | 50 | -1; // KB, -1 = unlimited
31
31
 
32
32
  // Format
33
- format: "markdown" | "json" | "plaintext";
33
+ format: "markdown" | "json" | "plaintext" | "mermaid";
34
34
 
35
35
  // Filters
36
36
  filterMode: "all" | "errors" | "success" | "pending";
@@ -204,6 +204,31 @@ export const COPY_PRESETS = {
204
204
  showStorageDiff: false,
205
205
  stripVerboseFields: true,
206
206
  },
207
+
208
+ /**
209
+ * Mermaid Diagram - Visual sequence diagram for flow visualization
210
+ */
211
+ mermaid: {
212
+ timestampFormat: "relative" as const,
213
+ includeSource: true,
214
+ includeStatus: true,
215
+ includeTitle: true,
216
+ includeSubtitle: false,
217
+ includeCorrelation: true,
218
+ includeDuration: true,
219
+ includeSummaryHeader: false,
220
+ includeTotalDuration: false,
221
+ includeEventData: false,
222
+ dataSizeThreshold: 1 as const,
223
+ format: "mermaid" as const,
224
+ filterMode: "all" as const,
225
+ filterSources: [],
226
+ compactMode: false,
227
+ smartJsonParsing: true,
228
+ reduxChangedOnly: true,
229
+ showStorageDiff: true,
230
+ stripVerboseFields: true,
231
+ },
207
232
  } as const;
208
233
 
209
234
  export type CopyPresetName = keyof typeof COPY_PRESETS;
@@ -266,4 +291,9 @@ export const PRESET_METADATA: Record<
266
291
  description: "Quick reference",
267
292
  color: "#6B7280", // Gray
268
293
  },
294
+ mermaid: {
295
+ label: "Diagram",
296
+ description: "Visual flow",
297
+ color: "#A855F7", // Purple
298
+ },
269
299
  };
@@ -15,7 +15,8 @@ export type EventSource =
15
15
  | "react-query"
16
16
  | "react-query-query"
17
17
  | "react-query-mutation"
18
- | "route";
18
+ | "route"
19
+ | "render";
19
20
 
20
21
  /**
21
22
  * Event status for visual indicators
@@ -83,6 +84,8 @@ export interface SourceInfo {
83
84
  count: number;
84
85
  /** Whether this source is currently enabled (listening and showing events) */
85
86
  enabled?: boolean;
87
+ /** Number of subscribers to this source's event store (for debugging) */
88
+ subscriberCount?: number;
86
89
  }
87
90
 
88
91
  /**