@buoy-gg/events 2.1.1

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 (84) hide show
  1. package/LICENSE +58 -0
  2. package/README.md +55 -0
  3. package/lib/commonjs/components/EventsCopySettingsView.js +645 -0
  4. package/lib/commonjs/components/EventsModal.js +263 -0
  5. package/lib/commonjs/components/ReactQueryEventDetail.js +428 -0
  6. package/lib/commonjs/components/UnifiedEventDetail.js +370 -0
  7. package/lib/commonjs/components/UnifiedEventFilters.js +113 -0
  8. package/lib/commonjs/components/UnifiedEventItem.js +349 -0
  9. package/lib/commonjs/components/UnifiedEventList.js +154 -0
  10. package/lib/commonjs/components/UnifiedEventViewer.js +126 -0
  11. package/lib/commonjs/hooks/useUnifiedEvents.js +237 -0
  12. package/lib/commonjs/index.js +205 -0
  13. package/lib/commonjs/package.json +1 -0
  14. package/lib/commonjs/preset.js +66 -0
  15. package/lib/commonjs/stores/unifiedEventStore.js +413 -0
  16. package/lib/commonjs/types/copySettings.js +220 -0
  17. package/lib/commonjs/types/index.js +17 -0
  18. package/lib/commonjs/utils/autoDiscoverEventSources.js +640 -0
  19. package/lib/commonjs/utils/badgeSelectionStorage.js +58 -0
  20. package/lib/commonjs/utils/copySettingsStorage.js +66 -0
  21. package/lib/commonjs/utils/correlationUtils.js +130 -0
  22. package/lib/commonjs/utils/eventExportFormatter.js +1095 -0
  23. package/lib/commonjs/utils/eventTransformers.js +496 -0
  24. package/lib/module/components/EventsCopySettingsView.js +641 -0
  25. package/lib/module/components/EventsModal.js +259 -0
  26. package/lib/module/components/ReactQueryEventDetail.js +424 -0
  27. package/lib/module/components/UnifiedEventDetail.js +366 -0
  28. package/lib/module/components/UnifiedEventFilters.js +109 -0
  29. package/lib/module/components/UnifiedEventItem.js +345 -0
  30. package/lib/module/components/UnifiedEventList.js +150 -0
  31. package/lib/module/components/UnifiedEventViewer.js +122 -0
  32. package/lib/module/hooks/useUnifiedEvents.js +234 -0
  33. package/lib/module/index.js +77 -0
  34. package/lib/module/preset.js +62 -0
  35. package/lib/module/stores/unifiedEventStore.js +387 -0
  36. package/lib/module/types/copySettings.js +215 -0
  37. package/lib/module/types/index.js +37 -0
  38. package/lib/module/utils/autoDiscoverEventSources.js +633 -0
  39. package/lib/module/utils/badgeSelectionStorage.js +52 -0
  40. package/lib/module/utils/copySettingsStorage.js +61 -0
  41. package/lib/module/utils/correlationUtils.js +120 -0
  42. package/lib/module/utils/eventExportFormatter.js +1085 -0
  43. package/lib/module/utils/eventTransformers.js +487 -0
  44. package/lib/typescript/components/EventsCopySettingsView.d.ts +16 -0
  45. package/lib/typescript/components/EventsModal.d.ts +16 -0
  46. package/lib/typescript/components/ReactQueryEventDetail.d.ts +15 -0
  47. package/lib/typescript/components/UnifiedEventDetail.d.ts +15 -0
  48. package/lib/typescript/components/UnifiedEventFilters.d.ts +21 -0
  49. package/lib/typescript/components/UnifiedEventItem.d.ts +26 -0
  50. package/lib/typescript/components/UnifiedEventList.d.ts +27 -0
  51. package/lib/typescript/components/UnifiedEventViewer.d.ts +8 -0
  52. package/lib/typescript/hooks/useUnifiedEvents.d.ts +30 -0
  53. package/lib/typescript/index.d.ts +28 -0
  54. package/lib/typescript/preset.d.ts +62 -0
  55. package/lib/typescript/stores/unifiedEventStore.d.ts +146 -0
  56. package/lib/typescript/types/copySettings.d.ts +179 -0
  57. package/lib/typescript/types/index.d.ts +73 -0
  58. package/lib/typescript/utils/autoDiscoverEventSources.d.ts +74 -0
  59. package/lib/typescript/utils/badgeSelectionStorage.d.ts +21 -0
  60. package/lib/typescript/utils/copySettingsStorage.d.ts +21 -0
  61. package/lib/typescript/utils/correlationUtils.d.ts +36 -0
  62. package/lib/typescript/utils/eventExportFormatter.d.ts +49 -0
  63. package/lib/typescript/utils/eventTransformers.d.ts +119 -0
  64. package/package.json +91 -0
  65. package/src/components/EventsCopySettingsView.tsx +742 -0
  66. package/src/components/EventsModal.tsx +328 -0
  67. package/src/components/ReactQueryEventDetail.tsx +413 -0
  68. package/src/components/UnifiedEventDetail.tsx +371 -0
  69. package/src/components/UnifiedEventFilters.tsx +156 -0
  70. package/src/components/UnifiedEventItem.tsx +396 -0
  71. package/src/components/UnifiedEventList.tsx +197 -0
  72. package/src/components/UnifiedEventViewer.tsx +132 -0
  73. package/src/hooks/useUnifiedEvents.ts +288 -0
  74. package/src/index.tsx +112 -0
  75. package/src/preset.tsx +57 -0
  76. package/src/stores/unifiedEventStore.ts +405 -0
  77. package/src/types/copySettings.ts +269 -0
  78. package/src/types/index.ts +96 -0
  79. package/src/utils/autoDiscoverEventSources.ts +690 -0
  80. package/src/utils/badgeSelectionStorage.ts +51 -0
  81. package/src/utils/copySettingsStorage.ts +61 -0
  82. package/src/utils/correlationUtils.ts +146 -0
  83. package/src/utils/eventExportFormatter.ts +1233 -0
  84. package/src/utils/eventTransformers.ts +567 -0
@@ -0,0 +1,742 @@
1
+ /**
2
+ * EventsCopySettingsView
3
+ *
4
+ * Settings UI for configuring event export format and content.
5
+ * Based on NetworkCopySettingsView pattern.
6
+ */
7
+
8
+ import { useCallback, useEffect, useMemo, useState } from "react";
9
+ import { Text, StyleSheet, ScrollView, View, TouchableOpacity } from "react-native";
10
+ import {
11
+ DynamicFilterView,
12
+ type DynamicFilterConfig,
13
+ macOSColors,
14
+ FileText,
15
+ FileCode,
16
+ Hash,
17
+ Zap,
18
+ AlertTriangle,
19
+ Settings,
20
+ Eye,
21
+ ChevronDown,
22
+ ChevronUp,
23
+ XCircle,
24
+ ToolbarCopyButton,
25
+ useFeatureGate,
26
+ ProFeatureBanner,
27
+ } from "@buoy-gg/shared-ui";
28
+ import { DataViewer } from "@buoy-gg/shared-ui/dataViewer";
29
+ import type { UnifiedEvent } from "../types";
30
+ import {
31
+ type EventsCopySettings,
32
+ DEFAULT_COPY_SETTINGS,
33
+ COPY_PRESETS,
34
+ PRESET_METADATA,
35
+ detectActivePreset,
36
+ type CopyPresetName,
37
+ } from "../types/copySettings";
38
+ import {
39
+ generateExport,
40
+ estimateExportSize,
41
+ getExportSummary,
42
+ } from "../utils/eventExportFormatter";
43
+ import {
44
+ loadCopySettings,
45
+ saveCopySettings,
46
+ } from "../utils/copySettingsStorage";
47
+
48
+ interface EventsCopySettingsViewProps {
49
+ events?: UnifiedEvent[];
50
+ initialSettings?: EventsCopySettings;
51
+ onSettingsChange?: (settings: EventsCopySettings) => void;
52
+ }
53
+
54
+ // Size thresholds for warnings
55
+ const SIZE_WARNING_THRESHOLD = 50 * 1024; // 50KB
56
+ const SIZE_DANGER_THRESHOLD = 200 * 1024; // 200KB
57
+
58
+ // Helper to format bytes
59
+ function formatBytes(bytes: number): string {
60
+ if (bytes < 1024) return `${bytes} B`;
61
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
62
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
63
+ }
64
+
65
+ // Mock events for preview when no real events exist
66
+ function createMockEvents(): UnifiedEvent[] {
67
+ const now = Date.now();
68
+ return [
69
+ {
70
+ id: "mock-1",
71
+ source: "storage-async",
72
+ timestamp: now - 2000,
73
+ title: "Set Item",
74
+ subtitle: "user_cart",
75
+ status: "success",
76
+ originalEvent: { action: "setItem", key: "user_cart", value: { items: 2 } },
77
+ },
78
+ {
79
+ id: "mock-2",
80
+ source: "react-query-query",
81
+ timestamp: now - 1500,
82
+ title: "cart \u203A items",
83
+ subtitle: "Fetching...",
84
+ status: "pending",
85
+ originalEvent: { type: "query-fetch-start", queryKey: ["cart", "items"] },
86
+ correlationId: "rq-query-cart",
87
+ sequenceInGroup: 1,
88
+ },
89
+ {
90
+ id: "mock-3",
91
+ source: "react-query-query",
92
+ timestamp: now - 1000,
93
+ title: "cart \u203A items",
94
+ subtitle: "Success \u00B7 500ms",
95
+ status: "success",
96
+ originalEvent: { type: "query-fetch-success", queryKey: ["cart", "items"], data: { items: [] } },
97
+ correlationId: "rq-query-cart",
98
+ sequenceInGroup: 2,
99
+ },
100
+ {
101
+ id: "mock-4",
102
+ source: "network",
103
+ timestamp: now - 500,
104
+ title: "POST /api/checkout",
105
+ subtitle: "402 \u00B7 450ms",
106
+ status: "error",
107
+ originalEvent: { method: "POST", url: "/api/checkout", status: 402, error: "Payment required" },
108
+ },
109
+ ];
110
+ }
111
+
112
+ export function EventsCopySettingsView({
113
+ events = [],
114
+ initialSettings = DEFAULT_COPY_SETTINGS,
115
+ onSettingsChange,
116
+ }: EventsCopySettingsViewProps) {
117
+ const { isPro } = useFeatureGate();
118
+ const [settings, setSettings] = useState<EventsCopySettings>(initialSettings);
119
+ const [isPreviewExpanded, setIsPreviewExpanded] = useState(false);
120
+ const [isLoaded, setIsLoaded] = useState(false);
121
+
122
+ // Load persisted settings on mount
123
+ useEffect(() => {
124
+ let mounted = true;
125
+ loadCopySettings().then((savedSettings) => {
126
+ if (mounted && savedSettings) {
127
+ setSettings(savedSettings);
128
+ onSettingsChange?.(savedSettings);
129
+ }
130
+ if (mounted) {
131
+ setIsLoaded(true);
132
+ }
133
+ });
134
+ return () => {
135
+ mounted = false;
136
+ };
137
+ }, [onSettingsChange]);
138
+
139
+ // Update internal state, notify parent, and persist
140
+ const updateSettings = useCallback(
141
+ (newSettings: EventsCopySettings) => {
142
+ setSettings(newSettings);
143
+ onSettingsChange?.(newSettings);
144
+ // Persist to storage
145
+ saveCopySettings(newSettings);
146
+ },
147
+ [onSettingsChange]
148
+ );
149
+
150
+ // Use mock events if no real events
151
+ const hasLiveData = events.length > 0;
152
+ const previewEvents = hasLiveData ? events : createMockEvents();
153
+
154
+ // Detect active preset
155
+ const activePreset = useMemo(() => detectActivePreset(settings), [settings]);
156
+
157
+ // Estimate size
158
+ const estimatedSize = useMemo(
159
+ () => estimateExportSize(previewEvents, settings),
160
+ [previewEvents, settings]
161
+ );
162
+
163
+ // Size warning level
164
+ const sizeWarningLevel = useMemo(() => {
165
+ if (estimatedSize >= SIZE_DANGER_THRESHOLD) return "danger";
166
+ if (estimatedSize >= SIZE_WARNING_THRESHOLD) return "warning";
167
+ return "none";
168
+ }, [estimatedSize]);
169
+
170
+ // Generate copy text
171
+ const generateCopyText = useCallback(() => {
172
+ return generateExport(previewEvents, settings);
173
+ }, [previewEvents, settings]);
174
+
175
+ // Handle preset selection
176
+ const applyPreset = useCallback(
177
+ (presetName: CopyPresetName) => {
178
+ const preset = COPY_PRESETS[presetName];
179
+ updateSettings({
180
+ ...preset,
181
+ // Convert readonly array to mutable array
182
+ filterSources: [...preset.filterSources],
183
+ });
184
+ },
185
+ [updateSettings]
186
+ );
187
+
188
+ // Handle option changes
189
+ const handleOptionChange = useCallback(
190
+ (optionId: string, value: unknown) => {
191
+ const [group, key] = optionId.split("::");
192
+
193
+ if (group === "preset") {
194
+ applyPreset(key as CopyPresetName);
195
+ return;
196
+ }
197
+
198
+ if (group === "include") {
199
+ updateSettings({
200
+ ...settings,
201
+ [key]: !settings[key as keyof EventsCopySettings],
202
+ });
203
+ return;
204
+ }
205
+
206
+ if (group === "format") {
207
+ updateSettings({
208
+ ...settings,
209
+ format: value as EventsCopySettings["format"],
210
+ });
211
+ return;
212
+ }
213
+
214
+ if (group === "timestamp") {
215
+ updateSettings({
216
+ ...settings,
217
+ timestampFormat: value as EventsCopySettings["timestampFormat"],
218
+ });
219
+ return;
220
+ }
221
+
222
+ if (group === "filter") {
223
+ updateSettings({
224
+ ...settings,
225
+ filterMode: value as EventsCopySettings["filterMode"],
226
+ });
227
+ return;
228
+ }
229
+
230
+ if (group === "threshold") {
231
+ updateSettings({
232
+ ...settings,
233
+ dataSizeThreshold: value as EventsCopySettings["dataSizeThreshold"],
234
+ });
235
+ return;
236
+ }
237
+ },
238
+ [settings, updateSettings, applyPreset]
239
+ );
240
+
241
+ // Render preview content
242
+ const renderPreviewContent = useCallback(() => {
243
+ const eventCount = previewEvents.length;
244
+
245
+ // Collapsed state
246
+ if (!isPreviewExpanded) {
247
+ const warningColor =
248
+ sizeWarningLevel === "danger"
249
+ ? macOSColors.semantic.error
250
+ : sizeWarningLevel === "warning"
251
+ ? macOSColors.semantic.warning
252
+ : macOSColors.text.secondary;
253
+
254
+ return (
255
+ <View style={styles.collapsedPreview}>
256
+ <TouchableOpacity
257
+ style={styles.expandButton}
258
+ onPress={() => setIsPreviewExpanded(true)}
259
+ >
260
+ <View style={styles.expandButtonContent}>
261
+ <Eye size={16} color={macOSColors.semantic.info} />
262
+ <View style={styles.expandButtonTextContainer}>
263
+ <Text style={styles.expandButtonTitle}>Show Preview</Text>
264
+ <Text style={styles.expandButtonSubtitle}>
265
+ {eventCount} event{eventCount !== 1 ? "s" : ""} \u00B7 ~
266
+ {formatBytes(estimatedSize)}
267
+ {!hasLiveData ? " (mock data)" : ""}
268
+ </Text>
269
+ </View>
270
+ <ChevronDown size={16} color={macOSColors.text.muted} />
271
+ </View>
272
+ </TouchableOpacity>
273
+
274
+ {sizeWarningLevel !== "none" && (
275
+ <View
276
+ style={[
277
+ styles.sizeWarning,
278
+ sizeWarningLevel === "danger" && styles.sizeWarningDanger,
279
+ ]}
280
+ >
281
+ <AlertTriangle size={12} color={warningColor} />
282
+ <Text style={[styles.sizeWarningText, { color: warningColor }]}>
283
+ {sizeWarningLevel === "danger"
284
+ ? "Very large payload - may cause performance issues"
285
+ : "Large payload - preview may be slow"}
286
+ </Text>
287
+ </View>
288
+ )}
289
+ </View>
290
+ );
291
+ }
292
+
293
+ // Expanded state
294
+ const exportText = generateCopyText();
295
+
296
+ return (
297
+ <View>
298
+ <TouchableOpacity
299
+ style={styles.collapseButton}
300
+ onPress={() => setIsPreviewExpanded(false)}
301
+ >
302
+ <Text style={styles.collapseButtonText}>Hide Preview</Text>
303
+ <ChevronUp size={14} color={macOSColors.text.secondary} />
304
+ </TouchableOpacity>
305
+
306
+ {settings.format === "json" ? (
307
+ <DataViewer
308
+ data={JSON.parse(exportText)}
309
+ title=""
310
+ showTypeFilter={false}
311
+ />
312
+ ) : (
313
+ <ScrollView style={styles.previewScroll} nestedScrollEnabled>
314
+ <Text style={styles.previewText} selectable={isPro}>
315
+ {exportText}
316
+ </Text>
317
+ </ScrollView>
318
+ )}
319
+ </View>
320
+ );
321
+ }, [
322
+ isPreviewExpanded,
323
+ previewEvents,
324
+ settings,
325
+ estimatedSize,
326
+ sizeWarningLevel,
327
+ hasLiveData,
328
+ generateCopyText,
329
+ isPro,
330
+ ]);
331
+
332
+ // Render preview header actions
333
+ const renderPreviewHeaderActions = useCallback(() => {
334
+ const summary = getExportSummary(previewEvents);
335
+ const statusLabel = hasLiveData
336
+ ? `${summary.totalEvents} events`
337
+ : "Mock data (no events captured)";
338
+
339
+ return (
340
+ <>
341
+ <Text style={styles.statusLabel}>{statusLabel}</Text>
342
+ <ToolbarCopyButton value={generateCopyText()} />
343
+ </>
344
+ );
345
+ }, [hasLiveData, previewEvents, generateCopyText]);
346
+
347
+ // Build filter config
348
+ const dynamicFilterConfig = useMemo<DynamicFilterConfig>(
349
+ () => ({
350
+ sections: [
351
+ // Presets Section
352
+ {
353
+ id: "presets",
354
+ title: "Presets",
355
+ type: "custom" as const,
356
+ data: [
357
+ {
358
+ id: "preset::llm",
359
+ label: PRESET_METADATA.llm.label,
360
+ icon: Zap,
361
+ color: PRESET_METADATA.llm.color,
362
+ value: "llm",
363
+ isActive: activePreset === "llm",
364
+ description: PRESET_METADATA.llm.description,
365
+ },
366
+ {
367
+ id: "preset::bugReport",
368
+ label: PRESET_METADATA.bugReport.label,
369
+ icon: AlertTriangle,
370
+ color: PRESET_METADATA.bugReport.color,
371
+ value: "bugReport",
372
+ isActive: activePreset === "bugReport",
373
+ description: PRESET_METADATA.bugReport.description,
374
+ },
375
+ {
376
+ id: "preset::json",
377
+ label: PRESET_METADATA.json.label,
378
+ icon: FileCode,
379
+ color: PRESET_METADATA.json.color,
380
+ value: "json",
381
+ isActive: activePreset === "json",
382
+ description: PRESET_METADATA.json.description,
383
+ },
384
+ {
385
+ id: "preset::errors",
386
+ label: PRESET_METADATA.errors.label,
387
+ icon: XCircle,
388
+ color: PRESET_METADATA.errors.color,
389
+ value: "errors",
390
+ isActive: activePreset === "errors",
391
+ description: PRESET_METADATA.errors.description,
392
+ },
393
+ {
394
+ id: "preset::minimal",
395
+ label: PRESET_METADATA.minimal.label,
396
+ icon: FileText,
397
+ color: PRESET_METADATA.minimal.color,
398
+ value: "minimal",
399
+ isActive: activePreset === "minimal",
400
+ description: PRESET_METADATA.minimal.description,
401
+ },
402
+ {
403
+ id: "preset::custom",
404
+ label: "Custom",
405
+ icon: Settings,
406
+ color: macOSColors.text.secondary,
407
+ value: "custom",
408
+ isActive: activePreset === null,
409
+ description: "User-configured",
410
+ },
411
+ ],
412
+ },
413
+
414
+ // Include Section
415
+ {
416
+ id: "include",
417
+ title: "Include",
418
+ type: "custom" as const,
419
+ data: [
420
+ {
421
+ id: "include::includeSource",
422
+ label: "Source",
423
+ value: "includeSource",
424
+ isActive: settings.includeSource,
425
+ },
426
+ {
427
+ id: "include::includeStatus",
428
+ label: "Status",
429
+ value: "includeStatus",
430
+ isActive: settings.includeStatus,
431
+ },
432
+ {
433
+ id: "include::includeTitle",
434
+ label: "Title",
435
+ value: "includeTitle",
436
+ isActive: settings.includeTitle,
437
+ },
438
+ {
439
+ id: "include::includeSubtitle",
440
+ label: "Subtitle",
441
+ value: "includeSubtitle",
442
+ isActive: settings.includeSubtitle,
443
+ },
444
+ {
445
+ id: "include::includeCorrelation",
446
+ label: "Correlation",
447
+ value: "includeCorrelation",
448
+ isActive: settings.includeCorrelation,
449
+ },
450
+ {
451
+ id: "include::includeDuration",
452
+ label: "Duration",
453
+ value: "includeDuration",
454
+ isActive: settings.includeDuration,
455
+ },
456
+ {
457
+ id: "include::includeSummaryHeader",
458
+ label: "Summary",
459
+ value: "includeSummaryHeader",
460
+ isActive: settings.includeSummaryHeader,
461
+ },
462
+ {
463
+ id: "include::includeEventData",
464
+ label: "Data",
465
+ value: "includeEventData",
466
+ isActive: settings.includeEventData,
467
+ },
468
+ ],
469
+ },
470
+
471
+ // Format Section
472
+ {
473
+ id: "format",
474
+ title: "Format",
475
+ type: "custom" as const,
476
+ data: [
477
+ {
478
+ id: "format::markdown",
479
+ label: "Markdown",
480
+ icon: FileText,
481
+ color: macOSColors.semantic.info,
482
+ value: "markdown",
483
+ isActive: settings.format === "markdown",
484
+ },
485
+ {
486
+ id: "format::json",
487
+ label: "JSON",
488
+ icon: FileCode,
489
+ color: macOSColors.semantic.warning,
490
+ value: "json",
491
+ isActive: settings.format === "json",
492
+ },
493
+ {
494
+ id: "format::plaintext",
495
+ label: "Text",
496
+ icon: Hash,
497
+ color: macOSColors.text.secondary,
498
+ value: "plaintext",
499
+ isActive: settings.format === "plaintext",
500
+ },
501
+ ],
502
+ },
503
+
504
+ // Timestamp Section
505
+ {
506
+ id: "timestamp",
507
+ title: "Timestamps",
508
+ type: "custom" as const,
509
+ data: [
510
+ {
511
+ id: "timestamp::relative",
512
+ label: "Relative",
513
+ value: "relative",
514
+ isActive: settings.timestampFormat === "relative",
515
+ description: "+0ms, +150ms",
516
+ },
517
+ {
518
+ id: "timestamp::absolute",
519
+ label: "Absolute",
520
+ value: "absolute",
521
+ isActive: settings.timestampFormat === "absolute",
522
+ description: "ISO format",
523
+ },
524
+ {
525
+ id: "timestamp::both",
526
+ label: "Both",
527
+ value: "both",
528
+ isActive: settings.timestampFormat === "both",
529
+ description: "Both formats",
530
+ },
531
+ ],
532
+ },
533
+
534
+ // Filter Section
535
+ {
536
+ id: "filter",
537
+ title: "Filter Events",
538
+ type: "custom" as const,
539
+ data: [
540
+ {
541
+ id: "filter::all",
542
+ label: "All",
543
+ value: "all",
544
+ isActive: settings.filterMode === "all",
545
+ },
546
+ {
547
+ id: "filter::errors",
548
+ label: "Errors",
549
+ icon: XCircle,
550
+ color: macOSColors.semantic.error,
551
+ value: "errors",
552
+ isActive: settings.filterMode === "errors",
553
+ },
554
+ {
555
+ id: "filter::success",
556
+ label: "Success",
557
+ color: macOSColors.semantic.success,
558
+ value: "success",
559
+ isActive: settings.filterMode === "success",
560
+ },
561
+ {
562
+ id: "filter::pending",
563
+ label: "Pending",
564
+ color: macOSColors.semantic.warning,
565
+ value: "pending",
566
+ isActive: settings.filterMode === "pending",
567
+ },
568
+ ],
569
+ },
570
+
571
+ // LLM Optimization Section
572
+ {
573
+ id: "llmOptimization",
574
+ title: "LLM Optimization",
575
+ type: "custom" as const,
576
+ data: [
577
+ {
578
+ id: "include::compactMode",
579
+ label: "Compact",
580
+ value: "compactMode",
581
+ isActive: settings.compactMode,
582
+ description: "One line per event",
583
+ },
584
+ {
585
+ id: "include::smartJsonParsing",
586
+ label: "Smart JSON",
587
+ value: "smartJsonParsing",
588
+ isActive: settings.smartJsonParsing,
589
+ description: "Parse nested JSON",
590
+ },
591
+ {
592
+ id: "include::reduxChangedOnly",
593
+ label: "Redux Diff",
594
+ value: "reduxChangedOnly",
595
+ isActive: settings.reduxChangedOnly,
596
+ description: "Only show changes",
597
+ },
598
+ {
599
+ id: "include::showStorageDiff",
600
+ label: "Storage Diff",
601
+ value: "showStorageDiff",
602
+ isActive: settings.showStorageDiff,
603
+ description: "Show what changed",
604
+ },
605
+ {
606
+ id: "include::stripVerboseFields",
607
+ label: "Strip Noise",
608
+ value: "stripVerboseFields",
609
+ isActive: settings.stripVerboseFields,
610
+ description: "Remove images/descriptions",
611
+ },
612
+ ],
613
+ },
614
+ ],
615
+ previewSection: {
616
+ enabled: true,
617
+ title: "PREVIEW",
618
+ icon: Eye,
619
+ content: renderPreviewContent,
620
+ headerActions: renderPreviewHeaderActions,
621
+ },
622
+ onFilterChange: handleOptionChange,
623
+ }),
624
+ [settings, activePreset, handleOptionChange, renderPreviewContent, renderPreviewHeaderActions]
625
+ );
626
+
627
+ // Pro gating - show banner at top with preview below (no floating)
628
+ if (!isPro) {
629
+ return (
630
+ <ScrollView style={styles.gatedContainer} contentContainerStyle={styles.gatedContent}>
631
+ <ProFeatureBanner
632
+ featureName="Events Export"
633
+ description="Export event flows to share with your team, debug issues, or feed into AI tools."
634
+ benefits={[
635
+ "Export to Markdown, JSON, or plain text",
636
+ "Customizable presets (LLM-ready, Bug Report, JSON)",
637
+ "Share event flows with teammates",
638
+ "Feed event context directly to AI assistants",
639
+ ]}
640
+ />
641
+ <View style={styles.gatedPreview}>
642
+ <DynamicFilterView {...dynamicFilterConfig} />
643
+ </View>
644
+ </ScrollView>
645
+ );
646
+ }
647
+
648
+ return <DynamicFilterView {...dynamicFilterConfig} />;
649
+ }
650
+
651
+ const styles = StyleSheet.create({
652
+ collapsedPreview: {
653
+ gap: 8,
654
+ },
655
+ expandButton: {
656
+ backgroundColor: macOSColors.background.input,
657
+ borderRadius: 8,
658
+ borderWidth: 1,
659
+ borderColor: macOSColors.border.default,
660
+ padding: 12,
661
+ },
662
+ expandButtonContent: {
663
+ flexDirection: "row",
664
+ alignItems: "center",
665
+ gap: 10,
666
+ },
667
+ expandButtonTextContainer: {
668
+ flex: 1,
669
+ },
670
+ expandButtonTitle: {
671
+ fontSize: 13,
672
+ fontWeight: "600",
673
+ color: macOSColors.text.primary,
674
+ },
675
+ expandButtonSubtitle: {
676
+ fontSize: 11,
677
+ color: macOSColors.text.secondary,
678
+ marginTop: 2,
679
+ },
680
+ collapseButton: {
681
+ flexDirection: "row",
682
+ alignItems: "center",
683
+ justifyContent: "center",
684
+ gap: 6,
685
+ paddingVertical: 8,
686
+ marginBottom: 8,
687
+ backgroundColor: macOSColors.background.hover,
688
+ borderRadius: 6,
689
+ borderWidth: 1,
690
+ borderColor: macOSColors.border.default,
691
+ },
692
+ collapseButtonText: {
693
+ fontSize: 12,
694
+ fontWeight: "500",
695
+ color: macOSColors.text.secondary,
696
+ },
697
+ sizeWarning: {
698
+ flexDirection: "row",
699
+ alignItems: "center",
700
+ gap: 6,
701
+ paddingHorizontal: 10,
702
+ paddingVertical: 6,
703
+ backgroundColor: macOSColors.semantic.warningBackground,
704
+ borderRadius: 6,
705
+ borderWidth: 1,
706
+ borderColor: macOSColors.semantic.warning + "30",
707
+ },
708
+ sizeWarningDanger: {
709
+ backgroundColor: macOSColors.semantic.errorBackground,
710
+ borderColor: macOSColors.semantic.error + "30",
711
+ },
712
+ sizeWarningText: {
713
+ fontSize: 11,
714
+ fontWeight: "500",
715
+ flex: 1,
716
+ },
717
+ previewScroll: {
718
+ maxHeight: 300,
719
+ },
720
+ previewText: {
721
+ fontFamily: "monospace",
722
+ fontSize: 11,
723
+ color: macOSColors.text.primary,
724
+ lineHeight: 18,
725
+ },
726
+ statusLabel: {
727
+ fontSize: 11,
728
+ fontWeight: "600",
729
+ color: macOSColors.text.muted,
730
+ marginRight: 8,
731
+ },
732
+ gatedContainer: {
733
+ flex: 1,
734
+ },
735
+ gatedContent: {
736
+ padding: 12,
737
+ gap: 12,
738
+ },
739
+ gatedPreview: {
740
+ // No flex: 1 so it doesn't try to fill remaining space
741
+ },
742
+ });