@buoy-gg/highlight-updates 7.0.35 → 7.0.38

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 (107) hide show
  1. package/README.md +28 -6
  2. package/lib/commonjs/highlight-updates/HighlightUpdatesOverlay.js +1 -281
  3. package/lib/commonjs/highlight-updates/components/HighlightFilterView.js +1 -1474
  4. package/lib/commonjs/highlight-updates/components/HighlightUpdatesModal.js +1 -641
  5. package/lib/commonjs/highlight-updates/components/IdentifierBadge.js +1 -273
  6. package/lib/commonjs/highlight-updates/components/IsolatedRenderList.js +1 -188
  7. package/lib/commonjs/highlight-updates/components/ModalHeaderContent.js +1 -293
  8. package/lib/commonjs/highlight-updates/components/RenderCauseBadge.js +1 -508
  9. package/lib/commonjs/highlight-updates/components/RenderDetailView.js +1 -846
  10. package/lib/commonjs/highlight-updates/components/RenderHistoryViewer.js +1 -915
  11. package/lib/commonjs/highlight-updates/components/RenderListItem.js +1 -235
  12. package/lib/commonjs/highlight-updates/components/RendersCopySettingsView.js +1 -466
  13. package/lib/commonjs/highlight-updates/components/StatsDisplay.js +1 -73
  14. package/lib/commonjs/highlight-updates/types/copySettings.js +1 -107
  15. package/lib/commonjs/highlight-updates/utils/CommitProfiler.js +1 -446
  16. package/lib/commonjs/highlight-updates/utils/HighlightUpdatesController.js +1 -1920
  17. package/lib/commonjs/highlight-updates/utils/PerformanceLogger.js +1 -359
  18. package/lib/commonjs/highlight-updates/utils/ProfilerInterceptor.js +1 -371
  19. package/lib/commonjs/highlight-updates/utils/RenderCauseDetector.js +1 -1840
  20. package/lib/commonjs/highlight-updates/utils/RenderTracker.js +1 -952
  21. package/lib/commonjs/highlight-updates/utils/ViewTypeMapper.js +1 -264
  22. package/lib/commonjs/highlight-updates/utils/copySettingsStorage.js +1 -32
  23. package/lib/commonjs/highlight-updates/utils/devtoolsComponentNames.js +1 -188
  24. package/lib/commonjs/highlight-updates/utils/namedComposite.js +1 -41
  25. package/lib/commonjs/highlight-updates/utils/ownStateChanged.js +1 -103
  26. package/lib/commonjs/highlight-updates/utils/rawEventEmitter.js +1 -50
  27. package/lib/commonjs/highlight-updates/utils/renderExportFormatter.js +1 -58
  28. package/lib/commonjs/highlight-updates/utils/rendersExportFormatter.js +1 -485
  29. package/lib/commonjs/highlight-updates/utils/screenElements.js +1 -1046
  30. package/lib/commonjs/highlight-updates/utils/tapMatch.js +1 -58
  31. package/lib/commonjs/highlight-updates/utils/testIDs.js +1 -68
  32. package/lib/commonjs/highlight-updates/utils/touchCapture.js +1 -277
  33. package/lib/commonjs/index.js +1 -429
  34. package/lib/commonjs/preset.js +1 -278
  35. package/lib/commonjs/sync/highlightUpdatesSyncAdapter.js +1 -235
  36. package/lib/module/highlight-updates/HighlightUpdatesOverlay.js +1 -274
  37. package/lib/module/highlight-updates/components/HighlightFilterView.js +1 -1468
  38. package/lib/module/highlight-updates/components/HighlightUpdatesModal.js +1 -634
  39. package/lib/module/highlight-updates/components/IdentifierBadge.js +1 -265
  40. package/lib/module/highlight-updates/components/IsolatedRenderList.js +1 -183
  41. package/lib/module/highlight-updates/components/ModalHeaderContent.js +1 -288
  42. package/lib/module/highlight-updates/components/RenderCauseBadge.js +1 -499
  43. package/lib/module/highlight-updates/components/RenderDetailView.js +1 -840
  44. package/lib/module/highlight-updates/components/RenderHistoryViewer.js +1 -908
  45. package/lib/module/highlight-updates/components/RenderListItem.js +1 -230
  46. package/lib/module/highlight-updates/components/RendersCopySettingsView.js +1 -462
  47. package/lib/module/highlight-updates/components/StatsDisplay.js +1 -70
  48. package/lib/module/highlight-updates/types/copySettings.js +1 -102
  49. package/lib/module/highlight-updates/utils/CommitProfiler.js +1 -442
  50. package/lib/module/highlight-updates/utils/HighlightUpdatesController.js +1 -1916
  51. package/lib/module/highlight-updates/utils/PerformanceLogger.js +1 -353
  52. package/lib/module/highlight-updates/utils/ProfilerInterceptor.js +1 -358
  53. package/lib/module/highlight-updates/utils/RenderCauseDetector.js +1 -1830
  54. package/lib/module/highlight-updates/utils/RenderTracker.js +1 -949
  55. package/lib/module/highlight-updates/utils/ViewTypeMapper.js +1 -255
  56. package/lib/module/highlight-updates/utils/copySettingsStorage.js +1 -26
  57. package/lib/module/highlight-updates/utils/devtoolsComponentNames.js +1 -178
  58. package/lib/module/highlight-updates/utils/namedComposite.js +1 -36
  59. package/lib/module/highlight-updates/utils/ownStateChanged.js +1 -98
  60. package/lib/module/highlight-updates/utils/rawEventEmitter.js +1 -46
  61. package/lib/module/highlight-updates/utils/renderExportFormatter.js +1 -54
  62. package/lib/module/highlight-updates/utils/rendersExportFormatter.js +1 -478
  63. package/lib/module/highlight-updates/utils/screenElements.js +1 -1040
  64. package/lib/module/highlight-updates/utils/tapMatch.js +1 -52
  65. package/lib/module/highlight-updates/utils/testIDs.js +1 -60
  66. package/lib/module/highlight-updates/utils/touchCapture.js +1 -270
  67. package/lib/module/index.js +1 -130
  68. package/lib/module/preset.js +1 -272
  69. package/lib/module/sync/highlightUpdatesSyncAdapter.js +1 -230
  70. package/lib/typescript/highlight-updates/utils/screenElements.d.ts +52 -1
  71. package/lib/typescript/highlight-updates/utils/tapMatch.d.ts +11 -0
  72. package/lib/typescript/sync/highlightUpdatesSyncAdapter.d.ts +1 -1
  73. package/package.json +8 -8
  74. package/lib/typescript/highlight-updates/HighlightUpdatesOverlay.d.ts.map +0 -1
  75. package/lib/typescript/highlight-updates/components/HighlightFilterView.d.ts.map +0 -1
  76. package/lib/typescript/highlight-updates/components/HighlightUpdatesModal.d.ts.map +0 -1
  77. package/lib/typescript/highlight-updates/components/IdentifierBadge.d.ts.map +0 -1
  78. package/lib/typescript/highlight-updates/components/IsolatedRenderList.d.ts.map +0 -1
  79. package/lib/typescript/highlight-updates/components/ModalHeaderContent.d.ts.map +0 -1
  80. package/lib/typescript/highlight-updates/components/RenderCauseBadge.d.ts.map +0 -1
  81. package/lib/typescript/highlight-updates/components/RenderDetailView.d.ts.map +0 -1
  82. package/lib/typescript/highlight-updates/components/RenderHistoryViewer.d.ts.map +0 -1
  83. package/lib/typescript/highlight-updates/components/RenderListItem.d.ts.map +0 -1
  84. package/lib/typescript/highlight-updates/components/RendersCopySettingsView.d.ts.map +0 -1
  85. package/lib/typescript/highlight-updates/components/StatsDisplay.d.ts.map +0 -1
  86. package/lib/typescript/highlight-updates/types/copySettings.d.ts.map +0 -1
  87. package/lib/typescript/highlight-updates/utils/CommitProfiler.d.ts.map +0 -1
  88. package/lib/typescript/highlight-updates/utils/HighlightUpdatesController.d.ts.map +0 -1
  89. package/lib/typescript/highlight-updates/utils/PerformanceLogger.d.ts.map +0 -1
  90. package/lib/typescript/highlight-updates/utils/ProfilerInterceptor.d.ts.map +0 -1
  91. package/lib/typescript/highlight-updates/utils/RenderCauseDetector.d.ts.map +0 -1
  92. package/lib/typescript/highlight-updates/utils/RenderTracker.d.ts.map +0 -1
  93. package/lib/typescript/highlight-updates/utils/ViewTypeMapper.d.ts.map +0 -1
  94. package/lib/typescript/highlight-updates/utils/copySettingsStorage.d.ts.map +0 -1
  95. package/lib/typescript/highlight-updates/utils/devtoolsComponentNames.d.ts.map +0 -1
  96. package/lib/typescript/highlight-updates/utils/namedComposite.d.ts.map +0 -1
  97. package/lib/typescript/highlight-updates/utils/ownStateChanged.d.ts.map +0 -1
  98. package/lib/typescript/highlight-updates/utils/rawEventEmitter.d.ts.map +0 -1
  99. package/lib/typescript/highlight-updates/utils/renderExportFormatter.d.ts.map +0 -1
  100. package/lib/typescript/highlight-updates/utils/rendersExportFormatter.d.ts.map +0 -1
  101. package/lib/typescript/highlight-updates/utils/screenElements.d.ts.map +0 -1
  102. package/lib/typescript/highlight-updates/utils/tapMatch.d.ts.map +0 -1
  103. package/lib/typescript/highlight-updates/utils/testIDs.d.ts.map +0 -1
  104. package/lib/typescript/highlight-updates/utils/touchCapture.d.ts.map +0 -1
  105. package/lib/typescript/index.d.ts.map +0 -1
  106. package/lib/typescript/preset.d.ts.map +0 -1
  107. package/lib/typescript/sync/highlightUpdatesSyncAdapter.d.ts.map +0 -1
@@ -1,1830 +1 @@
1
- /**
2
- * RenderCauseDetector
3
- *
4
- * Detects WHY a component rendered by comparing current fiber state
5
- * to previously stored state. Supports detection of:
6
- * - First mount
7
- * - Props changes (with changed key names)
8
- * - Hooks/state changes (with hook indices)
9
- * - Parent re-renders
10
- *
11
- * TWO-LEVEL CAUSATION:
12
- * 1. Native level: Why did the native view's props change? (style, accessibilityState, etc.)
13
- * 2. Component level: Why did the owning React component re-render? (props, state, parent)
14
- *
15
- * This gives the full picture:
16
- * "StepperValueDisplay re-rendered because PARENT, which caused native Text to update PROPS [style]"
17
- *
18
- * COMPONENT-SPECIFIC DETECTION:
19
- * Different native components have different important props:
20
- * - RCTText: `children` IS the text content - always track it
21
- * - RCTView: `children` is React elements - skip it
22
- * - RCTImageView: `source` is the image URL - track it
23
- *
24
- * See: RENDER_CAUSE_TEXT_COMPONENT_PLAN.md for full documentation
25
- */
26
-
27
- "use strict";
28
-
29
- // One implementation of "did this component's own state actually change?",
30
- // shared with CommitProfiler. The two classifiers are separate on purpose;
31
- // this question is not, and both of them got it wrong the same way.
32
- import { hookStateChanged } from "./ownStateChanged";
33
-
34
- // ============================================================================
35
- // TYPE DEFINITIONS
36
- // Based on React Native source: packages/react-native/Libraries/Renderer/
37
- // ============================================================================
38
-
39
- /**
40
- * React Fiber Work Tags
41
- * Identifies the type of fiber node in the tree
42
- *
43
- * Source: ReactWorkTags.js in React source
44
- */
45
-
46
- // IncompleteFunctionComponent
47
-
48
- /**
49
- * React Native Host Component Types
50
- * Native view types that we can detect and handle specifically
51
- *
52
- * Source: packages/react-native/Libraries/Components/
53
- */
54
-
55
- // Other native components
56
-
57
- /**
58
- * Component-specific prop detection configuration
59
- * Defines which props are meaningful for each native component type
60
- */
61
-
62
- /**
63
- * Detected hook type for categorization
64
- * Determines what kind of React hook we're looking at
65
- */
66
-
67
- /**
68
- * Extracted hook state with type information
69
- * Represents a single hook's current state for comparison
70
- */
71
-
72
- /**
73
- * Configuration for each native component type
74
- * Tells us which props are meaningful to track for render cause detection
75
- */
76
- const COMPONENT_PROP_CONFIGS = {
77
- // Text components - children IS the actual text content
78
- RCTText: {
79
- alwaysTrack: ["children"],
80
- // The displayed text/number
81
- skip: [],
82
- description: "Text component - children is the displayed content"
83
- },
84
- RCTVirtualText: {
85
- alwaysTrack: ["children"],
86
- // Nested text content
87
- skip: [],
88
- description: "Virtual Text (nested) - children is the displayed content"
89
- },
90
- // View - children is React elements, not meaningful for diff
91
- RCTView: {
92
- alwaysTrack: [],
93
- skip: ["children"],
94
- // React elements, not useful to track
95
- description: "View container - children are React elements"
96
- },
97
- // Image - source is the important prop
98
- RCTImageView: {
99
- alwaysTrack: ["source"],
100
- skip: ["children"],
101
- description: "Image - source contains the image URL/require"
102
- },
103
- // TextInput - value is important
104
- RCTTextInput: {
105
- alwaysTrack: ["value", "defaultValue"],
106
- skip: ["children"],
107
- description: "TextInput - value is the input content"
108
- },
109
- // Switch - value is the on/off state
110
- RCTSwitch: {
111
- alwaysTrack: ["value"],
112
- skip: ["children"],
113
- description: "Switch - value is the toggle state"
114
- },
115
- // Default for unknown components
116
- default: {
117
- alwaysTrack: [],
118
- skip: ["children"],
119
- // Skip children by default
120
- description: "Unknown component type"
121
- }
122
- };
123
-
124
- /**
125
- * Get the prop configuration for a native component type
126
- */
127
- function getComponentPropConfig(fiberType) {
128
- if (!fiberType) return COMPONENT_PROP_CONFIGS.default;
129
- return COMPONENT_PROP_CONFIGS[fiberType] || COMPONENT_PROP_CONFIGS.default;
130
- }
131
-
132
- /**
133
- * Stored fiber state for comparison between renders
134
- */
135
-
136
- // ============================================================================
137
- // COMPONENT FIBER STATE STORAGE
138
- // ============================================================================
139
-
140
- /**
141
- * Stored component fiber state for comparison between renders.
142
- * Now includes extracted hook states for Phase 3 hook value tracking.
143
- */
144
-
145
- /**
146
- * Storage for previous component fiber states
147
- *
148
- * IMPORTANT: React uses double-buffering with "current" and "work-in-progress" fibers.
149
- * The fiber reference changes between renders as React swaps the trees.
150
- * We need to check both the fiber AND its alternate when looking up state.
151
- *
152
- * WeakMap is used to automatically clean up when fibers are garbage collected.
153
- */
154
- const componentFiberStates = new WeakMap();
155
-
156
- /**
157
- * Get previous state for a component fiber, checking both current and alternate.
158
- *
159
- * React's reconciler maintains two fiber trees:
160
- * - "current" - the tree currently rendered to screen
161
- * - "workInProgress" - the tree being built for the next render
162
- *
163
- * These are linked via the `alternate` property. When a render completes,
164
- * they swap roles. So the fiber we see this render may be the alternate
165
- * of what we saw last render.
166
- *
167
- * See: packages/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js
168
- */
169
- function getComponentFiberPrevState(fiber) {
170
- if (!fiber) return undefined;
171
-
172
- // First, try the fiber itself
173
- let prev = componentFiberStates.get(fiber);
174
- if (prev) return prev;
175
-
176
- // If not found, try its alternate (the other side of double-buffering)
177
- if (fiber.alternate) {
178
- prev = componentFiberStates.get(fiber.alternate);
179
- if (prev) return prev;
180
- }
181
- return undefined;
182
- }
183
-
184
- /**
185
- * Store state for a component fiber.
186
- * Stores on both the fiber and its alternate to ensure we find it next render.
187
- */
188
- function setComponentFiberState(fiber, state) {
189
- if (!fiber) return;
190
-
191
- // Store on the current fiber
192
- componentFiberStates.set(fiber, state);
193
-
194
- // Also store on alternate if it exists, so we find it regardless of which
195
- // side of double-buffering we see next render
196
- if (fiber.alternate) {
197
- componentFiberStates.set(fiber.alternate, state);
198
- }
199
- }
200
-
201
- // Storage for previous fiber states
202
- const previousStates = new Map();
203
-
204
- // Configuration
205
- const MAX_STORED_STATES = 500;
206
- const MAX_CHANGED_KEYS = 10;
207
- const MAX_HOOK_DEPTH = 50;
208
- const MAX_PARENT_DEPTH = 50;
209
-
210
- // React Native internal components to skip when finding user components
211
- const INTERNAL_COMPONENT_NAMES = new Set([
212
- // React Native core primitives
213
- 'View', 'Text', 'TextImpl', 'Image', 'ScrollView', 'FlatList', 'SectionList', 'TouchableOpacity', 'TouchableHighlight', 'TouchableWithoutFeedback', 'Pressable', 'TextInput', 'Switch', 'ActivityIndicator', 'Modal', 'StatusBar', 'KeyboardAvoidingView',
214
- // Animated components
215
- 'AnimatedComponent', 'AnimatedComponentWrapper',
216
- // React internals
217
- 'Fragment', 'Suspense', 'Provider', 'Consumer', 'Context', 'ForwardRef',
218
- // Common wrapper names
219
- 'Unknown', 'Component']);
220
-
221
- /**
222
- * Check if a component name is an internal React Native component
223
- */
224
- function isInternalComponentName(name) {
225
- if (!name) return true;
226
- if (INTERNAL_COMPONENT_NAMES.has(name)) return true;
227
- // Skip Animated.* wrappers
228
- if (name.startsWith('Animated')) return true;
229
- return false;
230
- }
231
-
232
- /**
233
- * Get the owning React component fiber for a host fiber.
234
- * Walks up the fiber tree to find the first USER-DEFINED component,
235
- * skipping React Native internal components like View, Text, etc.
236
- */
237
- function getOwningComponentFiber(fiber) {
238
- if (!fiber) return null;
239
-
240
- // Walk up to find the component that owns this host element
241
- let current = fiber._debugOwner || fiber.return;
242
- let depth = 0;
243
- let firstComponentFiber = null; // Fallback if no user component found
244
-
245
- while (current && depth < 30) {
246
- // Function components have tag 0 (FunctionComponent) or 11 (ForwardRef)
247
- // Class components have tag 1 (ClassComponent)
248
- // Host components have tag 5 (HostComponent) - skip these
249
- const tag = current.tag;
250
-
251
- // Tags: 0=FunctionComponent, 1=ClassComponent, 11=ForwardRef, 15=SimpleMemoComponent
252
- if (tag === 0 || tag === 1 || tag === 11 || tag === 15) {
253
- const name = getComponentNameFromFiber(current);
254
-
255
- // Remember first component as fallback
256
- if (!firstComponentFiber) {
257
- firstComponentFiber = current;
258
- }
259
-
260
- // Return if this is a user-defined component (not internal)
261
- if (!isInternalComponentName(name)) {
262
- return current;
263
- }
264
- }
265
- current = current.return;
266
- depth++;
267
- }
268
-
269
- // Return first component found as fallback (even if internal)
270
- return firstComponentFiber;
271
- }
272
-
273
- /**
274
- * Get component name from a fiber
275
- */
276
- function getComponentNameFromFiber(fiber) {
277
- if (!fiber) return null;
278
- const type = fiber.type;
279
- if (type) {
280
- if (typeof type === 'string') return type;
281
- if (type.name) return type.name;
282
- if (type.displayName) return type.displayName;
283
- }
284
- return null;
285
- }
286
-
287
- // ============================================================================
288
- // HOOK STATE EXTRACTION (Phase 3)
289
- // ============================================================================
290
-
291
- /**
292
- * Detect the type of a hook based on its structure.
293
- *
294
- * React hooks have different shapes:
295
- * - useState/useReducer: Has a `queue` property with dispatch function
296
- * - useRef: memoizedState is { current: value }
297
- * - useMemo/useCallback: memoizedState is [value, deps] array
298
- * - useEffect/useLayoutEffect: memoizedState is an effect object with tag
299
- *
300
- * See: packages/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js
301
- */
302
- function detectHookType(hookState) {
303
- if (!hookState || typeof hookState !== 'object') {
304
- return 'unknown';
305
- }
306
-
307
- // useState/useReducer: has a queue with dispatch function
308
- // This is the most reliable indicator
309
- if (hookState.queue !== null && hookState.queue !== undefined) {
310
- // Could be useState or useReducer - both have queue
311
- // useReducer typically has a more complex reducer, but we can't easily distinguish
312
- return 'useState';
313
- }
314
- const memoizedState = hookState.memoizedState;
315
-
316
- // useRef: memoizedState is an object with only { current: value }
317
- if (memoizedState !== null && typeof memoizedState === 'object' && !Array.isArray(memoizedState) && 'current' in memoizedState && Object.keys(memoizedState).length === 1) {
318
- return 'useRef';
319
- }
320
-
321
- // useMemo/useCallback: memoizedState is [value, deps] array
322
- // The deps is an array used for dependency comparison
323
- if (Array.isArray(memoizedState) && memoizedState.length === 2) {
324
- const [, deps] = memoizedState;
325
- // If second element is an array (deps), it's likely useMemo/useCallback
326
- if (Array.isArray(deps) || deps === null) {
327
- // useCallback stores a function, useMemo stores any value
328
- if (typeof memoizedState[0] === 'function') {
329
- return 'useCallback';
330
- }
331
- return 'useMemo';
332
- }
333
- }
334
-
335
- // useEffect/useLayoutEffect: memoizedState has effect-specific properties
336
- if (memoizedState !== null && typeof memoizedState === 'object' && !Array.isArray(memoizedState) && ('tag' in memoizedState || 'create' in memoizedState || 'destroy' in memoizedState)) {
337
- return 'useEffect';
338
- }
339
- return 'unknown';
340
- }
341
-
342
- /**
343
- * Extract the actual value from a hook's memoizedState.
344
- * Different hooks store values differently.
345
- */
346
- function extractHookValue(hookState, hookType) {
347
- const memoizedState = hookState?.memoizedState;
348
- switch (hookType) {
349
- case 'useState':
350
- case 'useReducer':
351
- // useState/useReducer: memoizedState IS the value directly
352
- return memoizedState;
353
- case 'useRef':
354
- // useRef: memoizedState is { current: value }
355
- return memoizedState?.current;
356
- case 'useMemo':
357
- case 'useCallback':
358
- // useMemo/useCallback: memoizedState is [value, deps]
359
- return Array.isArray(memoizedState) ? memoizedState[0] : memoizedState;
360
- case 'useEffect':
361
- // useEffect: we don't really have a "value" to show
362
- return '[effect]';
363
- default:
364
- return memoizedState;
365
- }
366
- }
367
-
368
- /**
369
- * Extract all hook states from a fiber's memoizedState linked list.
370
- *
371
- * For function components, fiber.memoizedState is a linked list where each
372
- * node represents one hook call. The nodes are linked via the `next` property.
373
- *
374
- * This function walks the list and extracts:
375
- * - The hook's index (position in the list)
376
- * - The detected hook type (useState, useRef, useMemo, etc.)
377
- * - The actual value stored in the hook
378
- *
379
- * @param fiber - The component fiber (must be a function component)
380
- * @returns Array of extracted hook states, or null if no hooks
381
- */
382
- function extractHookStates(fiber) {
383
- if (!fiber?.memoizedState) return null;
384
-
385
- // Check if this looks like a hooks linked list (has 'next' property)
386
- // Class components have different memoizedState structure
387
- const firstHook = fiber.memoizedState;
388
- if (typeof firstHook !== 'object' || firstHook === null) {
389
- return null;
390
- }
391
-
392
- // If it doesn't have 'next', it might be a class component state object
393
- // or a single primitive value
394
- if (!('next' in firstHook) && !('queue' in firstHook) && !('memoizedState' in firstHook)) {
395
- return null;
396
- }
397
- const states = [];
398
- let hookState = firstHook;
399
- let index = 0;
400
-
401
- // Walk the linked list, with safety limit
402
- while (hookState !== null && index < MAX_HOOK_DEPTH) {
403
- const hookType = detectHookType(hookState);
404
- const value = extractHookValue(hookState, hookType);
405
- states.push({
406
- index,
407
- type: hookType,
408
- value,
409
- rawState: hookState.memoizedState
410
- });
411
- hookState = hookState.next;
412
- index++;
413
- }
414
- return states.length > 0 ? states : null;
415
- }
416
-
417
- /**
418
- * Compare two sets of hook states and find what changed.
419
- *
420
- * Returns an array of HookStateChange objects describing each change
421
- * with meaningful before/after values.
422
- *
423
- * @param prevStates - Previous hook states (from last render)
424
- * @param currentStates - Current hook states
425
- * @returns Array of changes, or null if no changes detected
426
- */
427
- function compareHookStates(prevStates, currentStates) {
428
- // Can't compare if either is missing
429
- if (!prevStates || !currentStates) return null;
430
- const changes = [];
431
-
432
- // Compare each hook by index
433
- const maxLength = Math.max(prevStates.length, currentStates.length);
434
- for (let i = 0; i < maxLength; i++) {
435
- const prev = prevStates[i];
436
- const curr = currentStates[i];
437
-
438
- // New hook added (shouldn't normally happen, but handle it)
439
- if (!prev && curr) {
440
- changes.push({
441
- index: i,
442
- type: curr.type,
443
- currentValue: formatHookValue(curr.value, curr.type),
444
- description: `Hook[${i}] added`
445
- });
446
- continue;
447
- }
448
-
449
- // Hook removed (shouldn't normally happen)
450
- if (prev && !curr) {
451
- changes.push({
452
- index: i,
453
- type: prev.type,
454
- previousValue: formatHookValue(prev.value, prev.type),
455
- description: `Hook[${i}] removed`
456
- });
457
- continue;
458
- }
459
-
460
- // Both exist - compare values
461
- if (prev && curr) {
462
- // Skip if raw state reference is the same (no change)
463
- if (prev.rawState === curr.rawState) {
464
- continue;
465
- }
466
-
467
- // For effects, skip (they have complex internal state)
468
- if (curr.type === 'useEffect') {
469
- continue;
470
- }
471
-
472
- // For useMemo/useCallback, the value changing means deps changed
473
- // Skip these as they're not typically interesting for debugging
474
- if (curr.type === 'useMemo' || curr.type === 'useCallback') {
475
- continue;
476
- }
477
-
478
- // Only report useState/useReducer/useRef changes (most useful)
479
- if (curr.type === 'useState' || curr.type === 'useReducer' || curr.type === 'useRef') {
480
- const prevFormatted = formatHookValue(prev.value, prev.type);
481
- const currFormatted = formatHookValue(curr.value, curr.type);
482
- changes.push({
483
- index: i,
484
- type: curr.type,
485
- previousValue: prevFormatted,
486
- currentValue: currFormatted,
487
- description: `${prevFormatted} → ${currFormatted}`
488
- });
489
- }
490
- }
491
- }
492
- return changes.length > 0 ? changes : null;
493
- }
494
-
495
- /**
496
- * Format a hook value for display.
497
- * Handles different value types appropriately.
498
- */
499
- function formatHookValue(value, hookType) {
500
- // For useRef, the value is already unwrapped
501
- if (hookType === 'useRef') {
502
- return formatDisplayValue(value);
503
- }
504
-
505
- // For useState/useReducer, value is the state directly
506
- return formatDisplayValue(value);
507
- }
508
-
509
- /**
510
- * Format any value for display, handling special cases.
511
- */
512
- function formatDisplayValue(value) {
513
- if (value === null) return null;
514
- if (value === undefined) return undefined;
515
- if (typeof value === 'boolean') return value;
516
- if (typeof value === 'number') return value;
517
- if (typeof value === 'string') {
518
- // Truncate long strings
519
- return value.length > 30 ? value.slice(0, 30) + '...' : value;
520
- }
521
- if (typeof value === 'function') {
522
- return `[Function: ${value.name || 'anonymous'}]`;
523
- }
524
- if (Array.isArray(value)) {
525
- return `[Array: ${value.length} items]`;
526
- }
527
- if (typeof value === 'object') {
528
- // For objects, just show type hint
529
- const keys = Object.keys(value);
530
- return `{Object: ${keys.length} keys}`;
531
- }
532
- return String(value);
533
- }
534
-
535
- // ============================================================================
536
- // COMPONENT CAUSE DETECTION
537
- // ============================================================================
538
-
539
- /**
540
- * Result of component cause detection.
541
- * Includes the cause type and detailed hook state changes (Phase 3).
542
- */
543
-
544
- /**
545
- * Detect why a React component re-rendered (component-level cause).
546
- * This is different from native-level cause - it tells you WHY the
547
- * React component function was called, not what native props changed.
548
- *
549
- * DOUBLE-BUFFERING HANDLING:
550
- * React maintains two fiber trees (current/workInProgress) that swap each render.
551
- * We use getComponentFiberPrevState() to check both the fiber and its alternate
552
- * when looking up previous state, ensuring we don't falsely detect "mount".
553
- *
554
- * SWAP DETECTION:
555
- * After React commits, fibers swap roles. The fiber we receive might be the "old"
556
- * fiber (pre-update values) with its alternate being the "new" fiber (post-update).
557
- * We detect this by comparing against our stored previous state and swap if needed.
558
- *
559
- * DETECTION LOGIC:
560
- * 1. If no previous state found → "mount" (first render)
561
- * 2. If memoizedProps changed → "props" (parent passed different props)
562
- * 3. If memoizedState changed → "state" (useState/useReducer updated)
563
- * 4. Otherwise → "parent" (parent re-rendered, no changes to this component)
564
- *
565
- * PHASE 3 ENHANCEMENT:
566
- * When state changes are detected, we also extract the actual hook values
567
- * and compare them to provide meaningful before/after information.
568
- */
569
- function detectComponentCause(componentFiber) {
570
- if (!componentFiber) return {
571
- cause: "unknown",
572
- hookChanges: null
573
- };
574
-
575
- // STRATEGY: Handle React's double-buffering swap
576
- // The fiber we receive might be the OLD fiber (with previous values) where
577
- // its alternate contains the NEW values. We need to detect and correct this.
578
- let currentFiber = componentFiber;
579
- let alternateFiber = componentFiber.alternate;
580
-
581
- // Check if we need to swap fiber and alternate using WeakMap stored state
582
- const storedPrev = getComponentFiberPrevState(componentFiber);
583
- if (storedPrev && alternateFiber) {
584
- // Strategy: Compare first useState hook values to determine which is current
585
- // The fiber whose state matches stored is the OLD fiber; swap it!
586
- const fiberHooks = extractHookStates(componentFiber);
587
- const altHooks = extractHookStates(alternateFiber);
588
- const storedHooks = storedPrev.extractedHooks;
589
- if (fiberHooks && altHooks && storedHooks && storedHooks.length > 0) {
590
- // Find first useState hook for comparison
591
- const storedStateHook = storedHooks.find(h => h.type === 'useState');
592
- if (storedStateHook) {
593
- const fiberStateHook = fiberHooks.find(h => h.index === storedStateHook.index);
594
- const altStateHook = altHooks.find(h => h.index === storedStateHook.index);
595
- if (fiberStateHook && altStateHook) {
596
- const fiberMatchesStored = fiberStateHook.value === storedStateHook.value;
597
- const altMatchesStored = altStateHook.value === storedStateHook.value;
598
-
599
- // If fiber matches stored but alternate doesn't, fiber is OLD - swap!
600
- if (fiberMatchesStored && !altMatchesStored) {
601
- currentFiber = alternateFiber;
602
- alternateFiber = componentFiber;
603
- }
604
- }
605
- }
606
- }
607
- }
608
-
609
- // Extract hook states from the CORRECT current fiber
610
- const currentHooks = extractHookStates(currentFiber);
611
-
612
- // Get previous state from alternate (which is now correctly the OLD fiber)
613
- let prevMemoizedProps = null;
614
- let prevMemoizedState = null;
615
- let prevExtractedHooks = null;
616
- if (alternateFiber) {
617
- // Alternate fiber available - use it as previous state
618
- prevMemoizedProps = alternateFiber.memoizedProps;
619
- prevMemoizedState = alternateFiber.memoizedState;
620
- prevExtractedHooks = extractHookStates(alternateFiber);
621
- } else {
622
- // Fall back to WeakMap storage (first render won't have alternate)
623
- if (storedPrev) {
624
- prevMemoizedProps = storedPrev.memoizedProps;
625
- prevMemoizedState = storedPrev.memoizedState;
626
- prevExtractedHooks = storedPrev.extractedHooks;
627
- }
628
- }
629
-
630
- // Store current state for next comparison (on both fiber and alternate)
631
- // This is critical for swap detection on next render
632
- setComponentFiberState(currentFiber, {
633
- memoizedProps: currentFiber.memoizedProps,
634
- memoizedState: currentFiber.memoizedState,
635
- extractedHooks: currentHooks
636
- });
637
-
638
- // First render - no previous state found (neither alternate nor WeakMap)
639
- if (prevMemoizedProps === null) {
640
- return {
641
- cause: "mount",
642
- hookChanges: null
643
- };
644
- }
645
-
646
- // Check if props changed (shallow comparison)
647
- // This means the parent passed different props to this component
648
- const propsChanged = !shallowEqual(prevMemoizedProps, currentFiber.memoizedProps);
649
- if (propsChanged) {
650
- return {
651
- cause: "props",
652
- hookChanges: null
653
- };
654
- }
655
-
656
- // Check if state changed (for hooks, memoizedState is a linked list)
657
- // This means useState/useReducer triggered a re-render
658
- // NOT `prevMemoizedState !== currentFiber.memoizedState`, which is what this
659
- // used to be — see utils/ownStateChanged.ts for why that is wrong and what
660
- // it cost. Shared with `CommitProfiler`, which reached the same wrong line
661
- // independently; the two classifiers stay separate, this one question does
662
- // not.
663
- if (hookStateChanged(prevMemoizedState, currentFiber.memoizedState, {
664
- isClassComponent: currentFiber.tag === 1
665
- })) {
666
- // Phase 3: the actual before/after values, for the UI. Only needed once
667
- // the cause is settled, and only the overlay displays them.
668
- const hookChanges = compareHookStates(prevExtractedHooks, currentHooks);
669
- return {
670
- cause: "state",
671
- hookChanges
672
- };
673
- }
674
-
675
- // If neither props nor state changed, it's a parent re-render cascade
676
- // This component re-rendered because its parent did, not because of its own changes
677
- return {
678
- cause: "parent",
679
- hookChanges: null
680
- };
681
- }
682
-
683
- /**
684
- * Shallow equality check for props objects
685
- */
686
- function shallowEqual(objA, objB) {
687
- if (objA === objB) return true;
688
- if (!objA || !objB) return false;
689
- if (typeof objA !== 'object' || typeof objB !== 'object') return false;
690
- const keysA = Object.keys(objA);
691
- const keysB = Object.keys(objB);
692
- if (keysA.length !== keysB.length) return false;
693
- for (const key of keysA) {
694
- if (objA[key] !== objB[key]) return false;
695
- }
696
- return true;
697
- }
698
-
699
- /**
700
- * Safe JSON stringify that handles circular references and functions
701
- */
702
- function safeStringify(obj, maxDepth = 3) {
703
- const seen = new WeakSet();
704
- function stringify(value, depth) {
705
- if (depth > maxDepth) return "[MAX_DEPTH]";
706
- if (value === null) return null;
707
- if (value === undefined) return undefined;
708
- if (typeof value === "function") return `[Function: ${value.name || "anonymous"}]`;
709
- if (typeof value === "symbol") return `[Symbol: ${value.toString()}]`;
710
- if (typeof value !== "object") return value;
711
- if (seen.has(value)) return "[Circular]";
712
- seen.add(value);
713
- if (Array.isArray(value)) {
714
- return value.slice(0, 10).map(v => stringify(v, depth + 1));
715
- }
716
- const result = {};
717
- const keys = Object.keys(value).slice(0, 20); // Limit keys
718
- for (const key of keys) {
719
- try {
720
- result[key] = stringify(value[key], depth + 1);
721
- } catch {
722
- result[key] = "[Error accessing]";
723
- }
724
- }
725
- return result;
726
- }
727
- return stringify(obj, 0);
728
- }
729
-
730
- /**
731
- * Log raw fiber debug data to console
732
- */
733
- function logRawFiberData(nativeTag, fiber, owningComponentFiber, prev, current, componentCause) {
734
- const componentName = getComponentNameFromFiber(owningComponentFiber) || "Unknown";
735
- console.log("\n========================================");
736
- console.log("[RN-BUOY DEBUG] RENDER EVENT");
737
- console.log("========================================");
738
- console.log(`Native Tag: ${nativeTag}`);
739
- console.log(`Component Name: ${componentName}`);
740
- console.log(`Is First Render: ${!prev}`);
741
- console.log(`Component Cause Detected: ${componentCause}`);
742
- console.log("----------------------------------------");
743
-
744
- // Native fiber (host component) data
745
- console.log("\n--- NATIVE FIBER (Host Component) ---");
746
- console.log("fiber.type:", fiber?.type);
747
- console.log("fiber.tag:", fiber?.tag);
748
- console.log("fiber.memoizedProps (CURRENT):", safeStringify(fiber?.memoizedProps));
749
- console.log("fiber.memoizedState:", safeStringify(fiber?.memoizedState));
750
- if (prev) {
751
- console.log("PREVIOUS memoizedProps:", safeStringify(prev.memoizedProps));
752
- console.log("PREVIOUS memoizedState:", safeStringify(prev.memoizedState));
753
- }
754
-
755
- // Component fiber (React component) data
756
- console.log("\n--- COMPONENT FIBER (React Component) ---");
757
- if (owningComponentFiber) {
758
- console.log("componentFiber.type:", owningComponentFiber?.type?.name || owningComponentFiber?.type);
759
- console.log("componentFiber.tag:", owningComponentFiber?.tag);
760
- console.log("componentFiber.memoizedProps:", safeStringify(owningComponentFiber?.memoizedProps));
761
- console.log("componentFiber.memoizedState:", safeStringify(owningComponentFiber?.memoizedState));
762
-
763
- // Try to extract hook state (memoizedState is a linked list for function components)
764
- console.log("\n--- HOOKS STATE (linked list walk) ---");
765
- let hookState = owningComponentFiber?.memoizedState;
766
- let hookIndex = 0;
767
- while (hookState && hookIndex < 10) {
768
- console.log(`Hook[${hookIndex}]:`, safeStringify({
769
- memoizedState: hookState.memoizedState,
770
- baseState: hookState.baseState,
771
- queue: hookState.queue ? "[Queue object]" : null
772
- }));
773
- hookState = hookState.next;
774
- hookIndex++;
775
- }
776
-
777
- // Phase 3: Show extracted hook states with type detection
778
- console.log("\n--- EXTRACTED HOOKS (Phase 3) ---");
779
- const extractedHooks = extractHookStates(owningComponentFiber);
780
- if (extractedHooks) {
781
- for (const hook of extractedHooks) {
782
- console.log(`Hook[${hook.index}] (${hook.type}):`, formatDisplayValue(hook.value));
783
- }
784
- } else {
785
- console.log("(No hooks extracted)");
786
- }
787
- } else {
788
- console.log("(No component fiber found)");
789
- }
790
-
791
- // Children/text content
792
- console.log("\n--- CHILDREN/TEXT CONTENT ---");
793
- const children = fiber?.memoizedProps?.children;
794
- console.log("children type:", typeof children);
795
- console.log("children value:", safeStringify(children));
796
- console.log("\n========================================\n");
797
- }
798
-
799
- /**
800
- * Get human-readable name for a fiber work tag
801
- */
802
- function getTagName(tag) {
803
- if (tag === undefined) return 'undefined';
804
- const tags = {
805
- 0: 'FunctionComponent',
806
- 1: 'ClassComponent',
807
- 2: 'IndeterminateComponent',
808
- 3: 'HostRoot',
809
- 4: 'HostPortal',
810
- 5: 'HostComponent',
811
- 6: 'HostText',
812
- 7: 'Fragment',
813
- 8: 'Mode',
814
- 9: 'ContextConsumer',
815
- 10: 'ContextProvider',
816
- 11: 'ForwardRef',
817
- 12: 'Profiler',
818
- 13: 'SuspenseComponent',
819
- 14: 'MemoComponent',
820
- 15: 'SimpleMemoComponent'
821
- };
822
- return tags[tag] || `Unknown(${tag})`;
823
- }
824
-
825
- // ============================================================================
826
- // DEBUG LOGGING FUNCTIONS (by level)
827
- // ============================================================================
828
-
829
- /**
830
- * MINIMAL logging - Only hook/state value changes
831
- * Shows just the essential info: "useState: 3334 → 3335"
832
- *
833
- * This is the most concise view for debugging state changes.
834
- */
835
- function logMinimal(componentName, componentCauseResult) {
836
- // Only log if there are actual hook changes
837
- if (!componentCauseResult.hookChanges || componentCauseResult.hookChanges.length === 0) {
838
- return;
839
- }
840
- for (const change of componentCauseResult.hookChanges) {
841
- console.log(`[${componentName || 'Unknown'}] ${change.type}[${change.index}]: ${change.previousValue} → ${change.currentValue}`);
842
- }
843
- }
844
-
845
- /**
846
- * VERBOSE logging - Component info + cause + value changes
847
- * Shows component context with the changes.
848
- */
849
- function logVerbose(nativeTag, fiber, owningComponentFiber, componentCauseResult, changedNativeProps) {
850
- const componentName = getComponentNameFromFiber(owningComponentFiber) || "Unknown";
851
- const nativeType = typeof fiber?.type === "string" ? fiber.type : "Unknown";
852
- const {
853
- cause,
854
- hookChanges
855
- } = componentCauseResult;
856
-
857
- // Single-line summary
858
- console.log(`[RENDER] ${componentName} (${nativeType}:${nativeTag}) | Cause: ${cause.toUpperCase()}` + (changedNativeProps && changedNativeProps.length > 0 ? ` | Props: [${changedNativeProps.join(', ')}]` : ''));
859
-
860
- // Hook changes on separate lines (if any)
861
- if (hookChanges && hookChanges.length > 0) {
862
- for (const change of hookChanges) {
863
- console.log(` └─ ${change.type}[${change.index}]: ${change.previousValue} → ${change.currentValue}`);
864
- }
865
- }
866
- }
867
-
868
- /**
869
- * Comprehensive render debug logging (level: "all").
870
- * Captures EVERYTHING available from the React fiber for analysis.
871
- *
872
- * This function is called when debugLogLevel is "all".
873
- * Use this to understand exactly what data is available for render cause detection.
874
- */
875
- function logComprehensiveRenderData(nativeTag, fiber, owningComponentFiber, prev, componentCauseResult, batchNativeTags, renderCount) {
876
- const componentName = getComponentNameFromFiber(owningComponentFiber) || "Unknown";
877
- const nativeType = typeof fiber?.type === "string" ? fiber.type : "Unknown";
878
- console.log(`\n[RN-BUOY RENDER DEBUG] ═══════════════════════════════════════`);
879
- console.log(`Render #${renderCount} for ${nativeType} (nativeTag: ${nativeTag})`);
880
- console.log(`Timestamp: ${new Date().toISOString()}`);
881
- console.log(`═══════════════════════════════════════════════════════════════\n`);
882
-
883
- // === NATIVE FIBER (Host Component) ===
884
- console.log(`NATIVE FIBER (${nativeType}):`);
885
- console.log(` type: "${fiber?.type}"`);
886
- console.log(` tag: ${fiber?.tag} (${getTagName(fiber?.tag)})`);
887
-
888
- // Use ALTERNATE for previous values (most reliable!)
889
- const altFiber = fiber?.alternate;
890
- const prevSource = altFiber ? 'alternate' : prev ? 'storage' : 'none';
891
- console.log(` Previous values source: ${prevSource}`);
892
-
893
- // Current and previous children - USE ALTERNATE!
894
- const currChildren = fiber?.memoizedProps?.children;
895
- const altChildren = altFiber?.memoizedProps?.children;
896
- const prevChildren = altChildren !== undefined ? altChildren : prev?.memoizedProps?.children;
897
- const childrenChanged = prevChildren !== undefined ? currChildren !== prevChildren : false;
898
- console.log(` memoizedProps.children: ${formatDisplayValue(currChildren)}${childrenChanged ? ` (was: ${formatDisplayValue(prevChildren)})` : prevChildren !== undefined ? ' (unchanged)' : ' (first render)'}`);
899
-
900
- // Style comparison - USE ALTERNATE!
901
- const currStyle = fiber?.memoizedProps?.style;
902
- const altStyle = altFiber?.memoizedProps?.style;
903
- const prevStyle = altStyle !== undefined ? altStyle : prev?.memoizedProps?.style;
904
- const styleChanged = prevStyle !== undefined ? currStyle !== prevStyle : false;
905
- if (currStyle) {
906
- console.log(` memoizedProps.style: ${JSON.stringify(safeStringify(currStyle, 2))}${styleChanged ? ' (REFERENCE CHANGED)' : ''}`);
907
- if (styleChanged && prevStyle) {
908
- const valuesEqual = deepEqual(currStyle, prevStyle);
909
- console.log(` └─ Values actually changed: ${!valuesEqual}`);
910
- }
911
- }
912
-
913
- // All other props
914
- console.log(` All memoizedProps keys: [${Object.keys(fiber?.memoizedProps || {}).join(', ')}]`);
915
-
916
- // Identifying props
917
- const testID = fiber?.memoizedProps?.testID;
918
- const nativeID = fiber?.memoizedProps?.nativeID;
919
- const accessibilityLabel = fiber?.memoizedProps?.accessibilityLabel;
920
- if (testID) console.log(` testID: "${testID}"`);
921
- if (nativeID) console.log(` nativeID: "${nativeID}"`);
922
- if (accessibilityLabel) console.log(` accessibilityLabel: "${accessibilityLabel}"`);
923
-
924
- // Alternate fiber info
925
- console.log(` alternate: ${altFiber ? 'YES' : 'NO'}`);
926
- if (altFiber) {
927
- console.log(` alternate.memoizedProps.children: ${formatDisplayValue(altChildren)}`);
928
- }
929
-
930
- // Fiber tree structure
931
- console.log(` Tree structure:`);
932
- console.log(` return (parent): ${fiber?.return ? getTagName(fiber.return.tag) : 'null'}`);
933
- console.log(` child: ${fiber?.child ? getTagName(fiber.child.tag) : 'null'}`);
934
- console.log(` sibling: ${fiber?.sibling ? getTagName(fiber.sibling.tag) : 'null'}`);
935
- console.log('');
936
-
937
- // === COMPONENT FIBER (React Component) ===
938
- console.log(`COMPONENT FIBER (${componentName}):`);
939
- if (owningComponentFiber) {
940
- console.log(` name: "${componentName}"`);
941
- console.log(` type: ${typeof owningComponentFiber.type} (${owningComponentFiber.type?.name || owningComponentFiber.type?.displayName || 'anonymous'})`);
942
- console.log(` tag: ${owningComponentFiber.tag} (${getTagName(owningComponentFiber.tag)})`);
943
-
944
- // Component props
945
- console.log(` memoizedProps: ${JSON.stringify(safeStringify(owningComponentFiber.memoizedProps, 2))}`);
946
-
947
- // Check ALTERNATE fiber (React's built-in previous state!)
948
- console.log(` alternate: ${owningComponentFiber.alternate ? 'YES' : 'NO'}`);
949
- if (owningComponentFiber.alternate) {
950
- console.log(` ALTERNATE memoizedProps: ${JSON.stringify(safeStringify(owningComponentFiber.alternate.memoizedProps, 2))}`);
951
- const altPropsChanged = !shallowEqual(owningComponentFiber.alternate.memoizedProps, owningComponentFiber.memoizedProps);
952
- console.log(` Props changed (vs alternate): ${altPropsChanged ? 'YES' : 'NO'}`);
953
-
954
- // Check state via alternate
955
- console.log(` ALTERNATE memoizedState: ${owningComponentFiber.alternate.memoizedState === owningComponentFiber.memoizedState ? 'SAME' : 'DIFFERENT'}`);
956
- }
957
-
958
- // Previous component state (from WeakMap - for comparison)
959
- const compPrev = getComponentFiberPrevState(owningComponentFiber);
960
- if (compPrev) {
961
- console.log(` WeakMap PREVIOUS memoizedProps: ${JSON.stringify(safeStringify(compPrev.memoizedProps, 2))}`);
962
- const propsChanged = !shallowEqual(compPrev.memoizedProps, owningComponentFiber.memoizedProps);
963
- console.log(` Props changed (vs WeakMap): ${propsChanged ? 'YES' : 'NO'}`);
964
- } else {
965
- console.log(` WeakMap PREVIOUS state: (not found - first render or WeakMap cleared)`);
966
- }
967
-
968
- // Debug owner chain
969
- if (owningComponentFiber._debugOwner) {
970
- const ownerName = getComponentNameFromFiber(owningComponentFiber._debugOwner);
971
- console.log(` _debugOwner: "${ownerName}"`);
972
-
973
- // Walk up owner chain
974
- let owner = owningComponentFiber._debugOwner;
975
- let depth = 1;
976
- while (owner && depth < 5) {
977
- const name = getComponentNameFromFiber(owner);
978
- console.log(` └─[${depth}] ${name || 'unknown'} (tag: ${owner.tag})`);
979
- owner = owner._debugOwner;
980
- depth++;
981
- }
982
- }
983
-
984
- // How far we walked to find this component
985
- let walkDepth = 0;
986
- let walker = fiber._debugOwner || fiber.return;
987
- while (walker && walker !== owningComponentFiber && walkDepth < 30) {
988
- walker = walker.return;
989
- walkDepth++;
990
- }
991
- console.log(` Depth from native fiber: ${walkDepth}`);
992
- } else {
993
- console.log(` (No component fiber found - could not walk up tree)`);
994
- }
995
- console.log('');
996
-
997
- // === HOOKS (For Function Components) ===
998
- console.log(`HOOKS:`);
999
- if (owningComponentFiber?.memoizedState) {
1000
- const hooks = extractHookStates(owningComponentFiber);
1001
-
1002
- // Try to get previous hooks from ALTERNATE fiber first (more reliable!)
1003
- const alternateHooks = owningComponentFiber.alternate ? extractHookStates(owningComponentFiber.alternate) : null;
1004
-
1005
- // Fall back to WeakMap storage
1006
- const compPrev = getComponentFiberPrevState(owningComponentFiber);
1007
- const prevHooks = alternateHooks || compPrev?.extractedHooks;
1008
- const prevSource = alternateHooks ? 'alternate' : compPrev ? 'WeakMap' : 'none';
1009
- if (hooks && hooks.length > 0) {
1010
- console.log(` Total hooks: ${hooks.length}`);
1011
- console.log(` Previous values source: ${prevSource}`);
1012
- hooks.forEach((hook, i) => {
1013
- const prevHook = prevHooks?.[i];
1014
- const changed = prevHook ? prevHook.rawState !== hook.rawState : false;
1015
- const prevValue = prevHook ? formatDisplayValue(prevHook.value) : 'N/A';
1016
- const marker = changed ? ' ← CHANGED' : '';
1017
- console.log(` [${i}] ${hook.type}: ${formatDisplayValue(hook.value)}${prevHook ? ` (was: ${prevValue})` : ' (first render)'}${marker}`);
1018
- });
1019
- } else {
1020
- console.log(` (Could not extract hooks - memoizedState structure not recognized)`);
1021
- console.log(` Raw memoizedState type: ${typeof owningComponentFiber.memoizedState}`);
1022
- console.log(` Has 'next' property: ${'next' in (owningComponentFiber.memoizedState || {})}`);
1023
- console.log(` Has 'queue' property: ${'queue' in (owningComponentFiber.memoizedState || {})}`);
1024
- }
1025
- } else {
1026
- console.log(` (No memoizedState - class component, no hooks, or not a function component)`);
1027
- }
1028
- console.log('');
1029
-
1030
- // === RAW HOOKS DATA (for deep debugging) ===
1031
- console.log(`RAW HOOKS DATA:`);
1032
- if (owningComponentFiber?.memoizedState && typeof owningComponentFiber.memoizedState === 'object') {
1033
- let hookState = owningComponentFiber.memoizedState;
1034
- let hookIndex = 0;
1035
- while (hookState && hookIndex < 10) {
1036
- console.log(` Hook[${hookIndex}]:`);
1037
- console.log(` memoizedState: ${safeStringify(hookState.memoizedState, 2)}`);
1038
- console.log(` baseState: ${safeStringify(hookState.baseState, 2)}`);
1039
- console.log(` queue: ${hookState.queue ? '[Queue present]' : 'null'}`);
1040
- console.log(` next: ${hookState.next ? '[Next hook]' : 'null'}`);
1041
- hookState = hookState.next;
1042
- hookIndex++;
1043
- }
1044
- if (hookIndex === 0) {
1045
- console.log(` (memoizedState is not a hooks linked list)`);
1046
- }
1047
- }
1048
- console.log('');
1049
-
1050
- // === DETECTION RESULTS ===
1051
- console.log(`DETECTION RESULTS:`);
1052
- console.log(` Component Cause: ${componentCauseResult.cause.toUpperCase()}`);
1053
- if (componentCauseResult.hookChanges && componentCauseResult.hookChanges.length > 0) {
1054
- console.log(` Hook Changes Detected:`);
1055
- componentCauseResult.hookChanges.forEach(change => {
1056
- console.log(` [${change.index}] ${change.type}: ${change.previousValue} → ${change.currentValue}`);
1057
- if (change.description) {
1058
- console.log(` ${change.description}`);
1059
- }
1060
- });
1061
- }
1062
-
1063
- // What our prop detection would find
1064
- const nativeType2 = typeof fiber?.type === "string" ? fiber.type : undefined;
1065
- const changedProps = getChangedKeys(prev?.memoizedProps, fiber?.memoizedProps, nativeType2);
1066
- if (changedProps && changedProps.length > 0) {
1067
- console.log(` Native Props Changed: [${changedProps.join(', ')}]`);
1068
- } else if (prev) {
1069
- console.log(` Native Props Changed: (none detected)`);
1070
- }
1071
- console.log('');
1072
-
1073
- // === BATCH CONTEXT ===
1074
- console.log(`BATCH CONTEXT:`);
1075
- console.log(` Batch size: ${batchNativeTags.size}`);
1076
- console.log(` All tags in batch: [${Array.from(batchNativeTags).slice(0, 20).join(', ')}${batchNativeTags.size > 20 ? '...' : ''}]`);
1077
- const parentTag = getParentNativeTag(fiber);
1078
- const parentInBatch = parentTag !== null && batchNativeTags.has(parentTag);
1079
- console.log(` Parent nativeTag: ${parentTag ?? 'not found'}`);
1080
- console.log(` Parent in batch: ${parentInBatch ? 'YES' : 'NO'}`);
1081
-
1082
- // Walk up to find parent components in batch
1083
- if (batchNativeTags.size > 1) {
1084
- console.log(` Components in same batch:`);
1085
- let parent = fiber?.return;
1086
- let depth = 0;
1087
- while (parent && depth < 10) {
1088
- const parentNT = getNativeTagFromStateNode(parent.stateNode);
1089
- if (parentNT !== null && batchNativeTags.has(parentNT)) {
1090
- const parentName = getComponentNameFromFiber(parent) || parent.type;
1091
- console.log(` [depth ${depth}] ${parentName} (tag: ${parentNT})`);
1092
- }
1093
- parent = parent.return;
1094
- depth++;
1095
- }
1096
- }
1097
- console.log(`\n═══════════════════════════════════════════════════════════════\n`);
1098
- }
1099
-
1100
- /**
1101
- * Detect why a component rendered
1102
- *
1103
- * @param nativeTag - The native tag of the component
1104
- * @param fiber - The React fiber object (host fiber)
1105
- * @param batchNativeTags - Set of all nativeTags in this render batch (for parent detection)
1106
- * @param debugLogLevel - Debug logging level: "off" | "minimal" | "verbose" | "all"
1107
- * @returns RenderCause object describing why the component rendered
1108
- */
1109
- export function detectRenderCause(nativeTag, fiber, batchNativeTags, debugLogLevel = "off") {
1110
- const now = Date.now();
1111
- if (!fiber) {
1112
- return {
1113
- type: "unknown",
1114
- timestamp: now
1115
- };
1116
- }
1117
-
1118
- // STRATEGY: React's double-buffering means fiber and fiber.alternate swap roles each render.
1119
- // The `internalInstanceHandle` we receive may point to either the "current" (just committed)
1120
- // or the "workInProgress" (about to be committed) fiber depending on timing.
1121
- //
1122
- // To reliably get current vs previous:
1123
- // 1. Check our stored previous state by nativeTag (we store AFTER each detection)
1124
- // 2. The current fiber's memoizedProps should be DIFFERENT from stored previous
1125
- // 3. If they're the same, we're looking at the alternate (need to swap)
1126
- //
1127
- // Detection: If fiber.memoizedProps === storedPrev.memoizedProps, we got the wrong fiber!
1128
-
1129
- let currentFiber = fiber;
1130
- let alternateFiber = fiber.alternate;
1131
-
1132
- // Check if we need to swap fiber and alternate
1133
- // This happens due to React's double-buffering - we might get the "old" fiber
1134
- const storedPrev = previousStates.get(nativeTag);
1135
- if (storedPrev && alternateFiber) {
1136
- // Detection strategy: Compare children values to determine which fiber is "current"
1137
- // The fiber with the DIFFERENT children value from stored is the NEW (current) fiber
1138
- // The fiber with the SAME children value as stored is the OLD (alternate) fiber
1139
- const storedChildren = storedPrev.memoizedProps?.children;
1140
- const fiberChildren = fiber.memoizedProps?.children;
1141
- const altChildren = alternateFiber.memoizedProps?.children;
1142
-
1143
- // Check by reference first (fastest), then by value for primitives
1144
- const fiberMatchesStored = fiber.memoizedProps === storedPrev.memoizedProps || storedChildren !== undefined && fiberChildren === storedChildren;
1145
- const altMatchesStored = alternateFiber.memoizedProps === storedPrev.memoizedProps || storedChildren !== undefined && altChildren === storedChildren;
1146
-
1147
- // If fiber matches stored but alternate doesn't, fiber is OLD - swap!
1148
- if (fiberMatchesStored && !altMatchesStored) {
1149
- currentFiber = alternateFiber;
1150
- alternateFiber = fiber;
1151
- if (debugLogLevel === "all") {
1152
- console.log(`[RenderCause] Detected fiber swap - using alternate as current`);
1153
- }
1154
- }
1155
- }
1156
-
1157
- // Get previous state from alternate fiber first, fall back to our Map
1158
- let prevMemoizedProps = null;
1159
- let prevMemoizedState = null;
1160
- if (alternateFiber) {
1161
- // Alternate fiber available - use it directly (most reliable!)
1162
- prevMemoizedProps = alternateFiber.memoizedProps;
1163
- prevMemoizedState = alternateFiber.memoizedState;
1164
- } else {
1165
- // Fall back to our Map storage (first render won't have alternate)
1166
- if (storedPrev) {
1167
- prevMemoizedProps = storedPrev.memoizedProps;
1168
- prevMemoizedState = storedPrev.memoizedState;
1169
- }
1170
- }
1171
-
1172
- // Build prev object for compatibility with existing code
1173
- const prev = prevMemoizedProps !== null ? {
1174
- memoizedProps: prevMemoizedProps,
1175
- memoizedState: prevMemoizedState,
1176
- timestamp: now
1177
- } : undefined;
1178
- const current = {
1179
- memoizedProps: currentFiber.memoizedProps,
1180
- memoizedState: currentFiber.memoizedState,
1181
- timestamp: now
1182
- };
1183
-
1184
- // Store current state for next comparison (as fallback for edge cases)
1185
- updateStoredState(nativeTag, current);
1186
-
1187
- // Get the owning React component for two-level causation
1188
- // Use currentFiber to ensure we walk up from the correct fiber
1189
- const owningComponentFiber = getOwningComponentFiber(currentFiber);
1190
- const componentName = getComponentNameFromFiber(owningComponentFiber) || undefined;
1191
-
1192
- // Get parent component name for cascade visualization
1193
- const parentComponentName = getParentComponentName(currentFiber, componentName) || undefined;
1194
-
1195
- // Phase 3: detectComponentCause now returns both cause and hook changes
1196
- const componentCauseResult = detectComponentCause(owningComponentFiber);
1197
- const componentCause = componentCauseResult.cause;
1198
- const componentHookChanges = componentCauseResult.hookChanges;
1199
-
1200
- // First mount detection - no alternate fiber AND no stored state
1201
- if (!prev) {
1202
- // Log mount event if logging is enabled
1203
- if (debugLogLevel !== "off") {
1204
- if (debugLogLevel === "minimal") {
1205
- console.log(`[${componentName || 'Unknown'}] MOUNT`);
1206
- } else if (debugLogLevel === "verbose") {
1207
- const nativeType = typeof currentFiber?.type === "string" ? currentFiber.type : "Unknown";
1208
- console.log(`[RENDER] ${componentName || 'Unknown'} (${nativeType}:${nativeTag}) | Cause: MOUNT`);
1209
- } else if (debugLogLevel === "all") {
1210
- const renderCount = previousStates.has(nativeTag) ? previousStates.size : 1;
1211
- logComprehensiveRenderData(nativeTag, currentFiber, owningComponentFiber, prev, componentCauseResult, batchNativeTags, renderCount);
1212
- }
1213
- }
1214
- return {
1215
- type: "mount",
1216
- timestamp: now,
1217
- componentCause: "mount",
1218
- // Override - if native is mount, component is too
1219
- componentName
1220
- };
1221
- }
1222
-
1223
- // Get the native component type for component-specific prop handling
1224
- // currentFiber.type is the native component name (e.g., "RCTText", "RCTView")
1225
- const nativeType = typeof currentFiber.type === "string" ? currentFiber.type : undefined;
1226
-
1227
- // Props change detection (native view props)
1228
- // Pass nativeType so we can handle component-specific props (e.g., children for Text)
1229
- const changedProps = getChangedKeys(prev.memoizedProps, current.memoizedProps, nativeType);
1230
-
1231
- // Debug logging based on level (for non-mount renders)
1232
- if (debugLogLevel !== "off") {
1233
- if (debugLogLevel === "minimal") {
1234
- // Only log if there are hook changes
1235
- logMinimal(componentName, componentCauseResult);
1236
- } else if (debugLogLevel === "verbose") {
1237
- logVerbose(nativeTag, currentFiber, owningComponentFiber, componentCauseResult, changedProps);
1238
- } else if (debugLogLevel === "all") {
1239
- const renderCount = previousStates.has(nativeTag) ? previousStates.size : 1;
1240
- logComprehensiveRenderData(nativeTag, currentFiber, owningComponentFiber, prev, componentCauseResult, batchNativeTags, renderCount);
1241
- }
1242
- }
1243
- if (changedProps && changedProps.length > 0) {
1244
- return {
1245
- type: "props",
1246
- changedKeys: changedProps.slice(0, MAX_CHANGED_KEYS),
1247
- timestamp: now,
1248
- componentCause,
1249
- componentName,
1250
- parentComponentName,
1251
- // Phase 3: Include hook changes when component cause is state
1252
- hookChanges: componentHookChanges || undefined
1253
- };
1254
- }
1255
-
1256
- // Hooks/State change detection (host fiber's hooks - usually none for host components)
1257
- const nativeHookChanges = detectHookChanges(prev.memoizedState, current.memoizedState);
1258
- if (nativeHookChanges && nativeHookChanges.length > 0) {
1259
- return {
1260
- type: "hooks",
1261
- hookIndices: nativeHookChanges,
1262
- timestamp: now,
1263
- componentCause,
1264
- componentName,
1265
- parentComponentName,
1266
- // Phase 3: Include hook changes when component cause is state
1267
- hookChanges: componentHookChanges || undefined
1268
- };
1269
- }
1270
-
1271
- // Parent re-rendered detection
1272
- // Check if parent fiber's nativeTag is also in this batch
1273
- const parentNativeTag = getParentNativeTag(currentFiber);
1274
- if (parentNativeTag && batchNativeTags.has(parentNativeTag)) {
1275
- return {
1276
- type: "parent",
1277
- timestamp: now,
1278
- componentCause,
1279
- componentName,
1280
- parentComponentName,
1281
- hookChanges: componentHookChanges || undefined
1282
- };
1283
- }
1284
-
1285
- // If we couldn't detect native-level cause, fall back to component cause
1286
- // This handles cases where the component re-rendered due to state but
1287
- // the native view props didn't change (or changed in undetectable ways)
1288
- if (componentCause === "state") {
1289
- return {
1290
- type: "state",
1291
- timestamp: now,
1292
- componentCause,
1293
- componentName,
1294
- parentComponentName,
1295
- // Phase 3: Include hook changes for state-caused renders
1296
- hookChanges: componentHookChanges || undefined
1297
- };
1298
- }
1299
- if (componentCause === "props") {
1300
- return {
1301
- type: "props",
1302
- timestamp: now,
1303
- componentCause,
1304
- componentName,
1305
- parentComponentName
1306
- };
1307
- }
1308
- if (componentCause === "parent") {
1309
- return {
1310
- type: "parent",
1311
- timestamp: now,
1312
- componentCause,
1313
- componentName,
1314
- parentComponentName
1315
- };
1316
- }
1317
- return {
1318
- type: "unknown",
1319
- timestamp: now,
1320
- componentCause,
1321
- componentName,
1322
- parentComponentName
1323
- };
1324
- }
1325
-
1326
- /**
1327
- * Shallow compare object keys to find changes
1328
- * Returns array of changed key names
1329
- *
1330
- * @param prev - Previous props object
1331
- * @param next - Current props object
1332
- * @param fiberType - Native component type (e.g., "RCTText", "RCTView")
1333
- * Used to determine which props are meaningful to track
1334
- *
1335
- * COMPONENT-SPECIFIC HANDLING:
1336
- * - RCTText/RCTVirtualText: `children` IS the text content, always track it
1337
- * - RCTView: `children` is React elements, skip it
1338
- * - RCTImageView: `source` is important, track it
1339
- *
1340
- * See: COMPONENT_PROP_CONFIGS for full configuration
1341
- */
1342
- function getChangedKeys(prev, next, fiberType) {
1343
- // Same reference = no changes
1344
- if (prev === next) return null;
1345
-
1346
- // Handle null/undefined cases
1347
- if (!prev || !next) return null;
1348
- if (typeof prev !== "object" || typeof next !== "object") return null;
1349
-
1350
- // Handle arrays (props shouldn't be arrays, but just in case)
1351
- if (Array.isArray(prev) || Array.isArray(next)) return null;
1352
-
1353
- // Get component-specific prop configuration
1354
- const propConfig = getComponentPropConfig(fiberType);
1355
- const allKeys = new Set([...Object.keys(prev), ...Object.keys(next)]);
1356
- const changed = [];
1357
- for (const key of allKeys) {
1358
- // Skip internal React props
1359
- if (key.startsWith("__")) continue;
1360
-
1361
- // Check if this key should be skipped for this component type
1362
- if (propConfig.skip.includes(key)) continue;
1363
-
1364
- // For `children` prop, use component-specific rules
1365
- if (key === "children") {
1366
- // Only track children if component config says to (e.g., RCTText)
1367
- if (!propConfig.alwaysTrack.includes("children")) {
1368
- continue; // Skip children for most components
1369
- }
1370
-
1371
- // For Text components, compare children values
1372
- // Children can be string, number, or array of mixed content
1373
- const prevChildren = prev[key];
1374
- const nextChildren = next[key];
1375
- if (prevChildren !== nextChildren) {
1376
- // Format the change nicely for display
1377
- if (isPrimitive(prevChildren) && isPrimitive(nextChildren)) {
1378
- // Show the actual value change: "children: 6 → 4"
1379
- changed.push(`children: ${formatValue(prevChildren)} → ${formatValue(nextChildren)}`);
1380
- } else {
1381
- // Complex children (arrays, objects) - just note it changed
1382
- changed.push("children (content)");
1383
- }
1384
- }
1385
- continue;
1386
- }
1387
-
1388
- // Standard shallow comparison for other props
1389
- if (prev[key] !== next[key]) {
1390
- // Phase 4: For object props like style, check if it's a reference-only change
1391
- const prevVal = prev[key];
1392
- const nextVal = next[key];
1393
- if (key === "style") {
1394
- // Style can be an object, array, or array with falsy values
1395
- // Deep compare to see if values actually changed
1396
- if (deepEqual(prevVal, nextVal)) {
1397
- // Reference changed but values are the same - mark as ref-only
1398
- changed.push(`${key} (ref only)`);
1399
- } else {
1400
- // Values actually changed
1401
- changed.push(key);
1402
- }
1403
- } else if (typeof prevVal === "function" && typeof nextVal === "function") {
1404
- // Functions are often recreated - note this for potential useCallback suggestion
1405
- changed.push(`${key} (fn ref)`);
1406
- } else {
1407
- changed.push(key);
1408
- }
1409
- }
1410
- }
1411
- return changed.length > 0 ? changed : null;
1412
- }
1413
-
1414
- /**
1415
- * Check if a value is a plain object (not array, null, or class instance)
1416
- */
1417
- function isPlainObject(value) {
1418
- if (value === null || typeof value !== "object") return false;
1419
- if (Array.isArray(value)) return false;
1420
- // Check if it's a plain object (not a class instance)
1421
- const proto = Object.getPrototypeOf(value);
1422
- return proto === Object.prototype || proto === null;
1423
- }
1424
-
1425
- /**
1426
- * Deep equality check for objects (used for style comparison)
1427
- * Returns true if objects have the same values
1428
- */
1429
- function deepEqual(obj1, obj2, depth = 0) {
1430
- // Prevent infinite recursion
1431
- if (depth > 5) return false;
1432
-
1433
- // Same reference
1434
- if (obj1 === obj2) return true;
1435
-
1436
- // Type check
1437
- if (typeof obj1 !== typeof obj2) return false;
1438
-
1439
- // Primitives
1440
- if (typeof obj1 !== "object" || obj1 === null || obj2 === null) {
1441
- return obj1 === obj2;
1442
- }
1443
-
1444
- // Arrays
1445
- if (Array.isArray(obj1) !== Array.isArray(obj2)) return false;
1446
- if (Array.isArray(obj1)) {
1447
- if (obj1.length !== obj2.length) return false;
1448
- for (let i = 0; i < obj1.length; i++) {
1449
- if (!deepEqual(obj1[i], obj2[i], depth + 1)) return false;
1450
- }
1451
- return true;
1452
- }
1453
-
1454
- // Objects
1455
- const keys1 = Object.keys(obj1);
1456
- const keys2 = Object.keys(obj2);
1457
- if (keys1.length !== keys2.length) return false;
1458
- for (const key of keys1) {
1459
- if (!keys2.includes(key)) return false;
1460
- if (!deepEqual(obj1[key], obj2[key], depth + 1)) return false;
1461
- }
1462
- return true;
1463
- }
1464
-
1465
- /**
1466
- * Check if a value is a primitive (string, number, boolean, null, undefined)
1467
- */
1468
- function isPrimitive(value) {
1469
- return value === null || value === undefined || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1470
- }
1471
-
1472
- /**
1473
- * Format a value for display in change messages
1474
- */
1475
- function formatValue(value) {
1476
- if (value === null) return "null";
1477
- if (value === undefined) return "undefined";
1478
- if (typeof value === "string") {
1479
- // Truncate long strings
1480
- return value.length > 20 ? `"${value.slice(0, 20)}..."` : `"${value}"`;
1481
- }
1482
- if (typeof value === "number") return String(value);
1483
- if (typeof value === "boolean") return String(value);
1484
- return "[object]";
1485
- }
1486
-
1487
- /**
1488
- * Detect which hooks changed by walking the linked list
1489
- * Returns array of hook indices that changed
1490
- */
1491
- function detectHookChanges(prevState, nextState) {
1492
- // Same reference = no changes
1493
- if (prevState === nextState) return null;
1494
-
1495
- // If either is null/undefined, can't compare
1496
- if (prevState == null || nextState == null) return null;
1497
-
1498
- // Check if this looks like a hooks linked list
1499
- // Hooks have a 'next' property forming a linked list
1500
- if (typeof prevState !== "object" || typeof nextState !== "object") {
1501
- return null;
1502
- }
1503
- const changes = [];
1504
- let prevHook = prevState;
1505
- let nextHook = nextState;
1506
- let index = 0;
1507
-
1508
- // Walk the hooks linked list
1509
- while (nextHook !== null && index < MAX_HOOK_DEPTH) {
1510
- if (prevHook === null) {
1511
- // New hook added (shouldn't happen normally, but handle it)
1512
- changes.push(index);
1513
- } else if (didHookChange(prevHook, nextHook)) {
1514
- changes.push(index);
1515
- }
1516
-
1517
- // Move to next hook in list
1518
- nextHook = nextHook?.next ?? null;
1519
- prevHook = prevHook?.next ?? null;
1520
- index++;
1521
- }
1522
- return changes.length > 0 ? changes : null;
1523
- }
1524
-
1525
- /**
1526
- * Check if a single hook changed
1527
- */
1528
- function didHookChange(prev, next) {
1529
- if (prev === next) return false;
1530
-
1531
- // Check memoizedState (useState, useReducer, useMemo, useCallback, useRef)
1532
- if (prev.memoizedState !== next.memoizedState) {
1533
- return true;
1534
- }
1535
-
1536
- // Check baseState (useReducer specific)
1537
- if (prev.baseState !== undefined && prev.baseState !== next.baseState) {
1538
- return true;
1539
- }
1540
- return false;
1541
- }
1542
-
1543
- /**
1544
- * Get the nativeTag of the nearest parent host component
1545
- */
1546
- function getParentNativeTag(fiber) {
1547
- let parent = fiber?.return;
1548
- let depth = 0;
1549
- while (parent && depth < MAX_PARENT_DEPTH) {
1550
- // Check if this is a host component with a stateNode
1551
- if (parent.stateNode) {
1552
- const tag = getNativeTagFromStateNode(parent.stateNode);
1553
- if (tag != null) return tag;
1554
- }
1555
- parent = parent.return;
1556
- depth++;
1557
- }
1558
- return null;
1559
- }
1560
-
1561
- /**
1562
- * Get the parent React component name for cascade visualization.
1563
- * Walks up the fiber tree to find the nearest user-defined component
1564
- * that is DIFFERENT from the current component.
1565
- */
1566
- function getParentComponentName(fiber, currentComponentName) {
1567
- if (!fiber) return null;
1568
-
1569
- // Start from the owning component's parent
1570
- const owningFiber = getOwningComponentFiber(fiber);
1571
- if (!owningFiber) return null;
1572
- let parent = owningFiber.return;
1573
- let depth = 0;
1574
- while (parent && depth < MAX_PARENT_DEPTH) {
1575
- const tag = parent.tag;
1576
-
1577
- // Tags: 0=FunctionComponent, 1=ClassComponent, 11=ForwardRef, 15=SimpleMemoComponent
1578
- if (tag === 0 || tag === 1 || tag === 11 || tag === 15) {
1579
- const name = getComponentNameFromFiber(parent);
1580
-
1581
- // Skip internal React components and same-name components
1582
- if (name && !isInternalComponentName(name) && name !== currentComponentName) {
1583
- return name;
1584
- }
1585
- }
1586
- parent = parent.return;
1587
- depth++;
1588
- }
1589
- return null;
1590
- }
1591
-
1592
- /**
1593
- * Extract nativeTag from various stateNode formats
1594
- */
1595
- function getNativeTagFromStateNode(stateNode) {
1596
- if (!stateNode) return null;
1597
-
1598
- // Direct __nativeTag (Fabric)
1599
- if (typeof stateNode.__nativeTag === "number") {
1600
- return stateNode.__nativeTag;
1601
- }
1602
-
1603
- // Direct _nativeTag (Legacy/Paper)
1604
- if (typeof stateNode._nativeTag === "number") {
1605
- return stateNode._nativeTag;
1606
- }
1607
-
1608
- // Fabric canonical path
1609
- if (typeof stateNode.canonical?.__nativeTag === "number") {
1610
- return stateNode.canonical.__nativeTag;
1611
- }
1612
-
1613
- // Public instance path
1614
- if (typeof stateNode.canonical?.publicInstance?.__nativeTag === "number") {
1615
- return stateNode.canonical.publicInstance.__nativeTag;
1616
- }
1617
- return null;
1618
- }
1619
-
1620
- /**
1621
- * Update stored state with automatic cleanup when limit is reached
1622
- */
1623
- function updateStoredState(nativeTag, state) {
1624
- // Enforce max limit - remove oldest 25% when full
1625
- if (previousStates.size >= MAX_STORED_STATES) {
1626
- const entries = Array.from(previousStates.entries());
1627
- // Sort by timestamp (oldest first)
1628
- entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
1629
-
1630
- // Remove oldest 25%
1631
- const removeCount = Math.floor(MAX_STORED_STATES / 4);
1632
- for (let i = 0; i < removeCount; i++) {
1633
- previousStates.delete(entries[i][0]);
1634
- }
1635
- }
1636
- previousStates.set(nativeTag, state);
1637
- }
1638
-
1639
- /**
1640
- * Clear all stored states
1641
- * Call this when tracking is disabled to prevent memory leaks
1642
- */
1643
- export function clearRenderCauseState() {
1644
- previousStates.clear();
1645
- }
1646
-
1647
- /**
1648
- * Remove a specific component from state storage
1649
- * Useful when a component is unmounted
1650
- */
1651
- export function removeRenderCauseState(nativeTag) {
1652
- previousStates.delete(nativeTag);
1653
- }
1654
-
1655
- /**
1656
- * Get storage stats for debugging/display
1657
- */
1658
- export function getRenderCauseStats() {
1659
- return {
1660
- storedStates: previousStates.size,
1661
- maxStates: MAX_STORED_STATES
1662
- };
1663
- }
1664
-
1665
- // === Props/State Snapshot Capture for History ===
1666
-
1667
- // Max depth for cloning nested objects
1668
- const MAX_CLONE_DEPTH = 5;
1669
- // Max string length for values
1670
- const MAX_STRING_LENGTH = 200;
1671
- // Max array items to include
1672
- const MAX_ARRAY_ITEMS = 20;
1673
- // Max object keys to include
1674
- const MAX_OBJECT_KEYS = 30;
1675
-
1676
- /**
1677
- * Safely clone props/state for history storage.
1678
- * Handles circular references, functions, and large objects.
1679
- */
1680
- export function safeCloneForHistory(value, depth = 0, seen = new WeakSet()) {
1681
- // Handle primitives
1682
- if (value === null || value === undefined) return value;
1683
- if (typeof value === "boolean" || typeof value === "number") return value;
1684
-
1685
- // Handle strings (truncate long ones)
1686
- if (typeof value === "string") {
1687
- return value.length > MAX_STRING_LENGTH ? value.slice(0, MAX_STRING_LENGTH) + "..." : value;
1688
- }
1689
-
1690
- // Handle functions - show name or placeholder
1691
- if (typeof value === "function") {
1692
- return `[Function: ${value.name || "anonymous"}]`;
1693
- }
1694
-
1695
- // Handle symbols
1696
- if (typeof value === "symbol") {
1697
- return `[Symbol: ${value.description || ""}]`;
1698
- }
1699
-
1700
- // Stop at max depth
1701
- if (depth >= MAX_CLONE_DEPTH) {
1702
- if (Array.isArray(value)) return `[Array: ${value.length} items]`;
1703
- if (typeof value === "object") {
1704
- const keys = Object.keys(value);
1705
- return `[Object: ${keys.length} keys]`;
1706
- }
1707
- return "[...]";
1708
- }
1709
-
1710
- // Handle circular references
1711
- if (typeof value === "object") {
1712
- if (seen.has(value)) {
1713
- return "[Circular]";
1714
- }
1715
- seen.add(value);
1716
- }
1717
-
1718
- // Handle arrays
1719
- if (Array.isArray(value)) {
1720
- const cloned = value.slice(0, MAX_ARRAY_ITEMS).map(item => safeCloneForHistory(item, depth + 1, seen));
1721
- if (value.length > MAX_ARRAY_ITEMS) {
1722
- cloned.push(`[...${value.length - MAX_ARRAY_ITEMS} more]`);
1723
- }
1724
- return cloned;
1725
- }
1726
-
1727
- // Handle Date
1728
- if (value instanceof Date) {
1729
- return value.toISOString();
1730
- }
1731
-
1732
- // Handle Error
1733
- if (value instanceof Error) {
1734
- return `[Error: ${value.message}]`;
1735
- }
1736
-
1737
- // Handle RegExp
1738
- if (value instanceof RegExp) {
1739
- return value.toString();
1740
- }
1741
-
1742
- // Handle Map
1743
- if (value instanceof Map) {
1744
- const obj = {
1745
- __type: "Map",
1746
- __size: value.size
1747
- };
1748
- let count = 0;
1749
- for (const [k, v] of value) {
1750
- if (count >= MAX_OBJECT_KEYS) {
1751
- obj[`...${value.size - count} more`] = true;
1752
- break;
1753
- }
1754
- const key = typeof k === "string" ? k : String(k);
1755
- obj[key] = safeCloneForHistory(v, depth + 1, seen);
1756
- count++;
1757
- }
1758
- return obj;
1759
- }
1760
-
1761
- // Handle Set
1762
- if (value instanceof Set) {
1763
- return {
1764
- __type: "Set",
1765
- __size: value.size,
1766
- values: Array.from(value).slice(0, MAX_ARRAY_ITEMS).map(item => safeCloneForHistory(item, depth + 1, seen))
1767
- };
1768
- }
1769
-
1770
- // Handle plain objects
1771
- if (typeof value === "object") {
1772
- const cloned = {};
1773
- const keys = Object.keys(value);
1774
- const keysToClone = keys.slice(0, MAX_OBJECT_KEYS);
1775
- for (const key of keysToClone) {
1776
- // Skip internal React props
1777
- if (key.startsWith("__") || key === "children") continue;
1778
- try {
1779
- cloned[key] = safeCloneForHistory(value[key], depth + 1, seen);
1780
- } catch {
1781
- cloned[key] = "[Error accessing property]";
1782
- }
1783
- }
1784
- if (keys.length > MAX_OBJECT_KEYS) {
1785
- cloned[`...${keys.length - MAX_OBJECT_KEYS} more keys`] = true;
1786
- }
1787
- return cloned;
1788
- }
1789
- return "[Unknown type]";
1790
- }
1791
-
1792
- /**
1793
- * Capture props snapshot from a fiber for history storage
1794
- */
1795
- export function capturePropsSnapshot(fiber) {
1796
- if (!fiber?.memoizedProps) return undefined;
1797
- return safeCloneForHistory(fiber.memoizedProps);
1798
- }
1799
-
1800
- /**
1801
- * Capture state snapshot from a fiber for history storage
1802
- * For function components, this walks the hooks linked list
1803
- */
1804
- export function captureStateSnapshot(fiber) {
1805
- if (!fiber?.memoizedState) return undefined;
1806
- const state = fiber.memoizedState;
1807
-
1808
- // Check if it's a hooks linked list (has 'next' property)
1809
- if (typeof state === "object" && state !== null && "next" in state) {
1810
- // It's a hooks list - extract hook values
1811
- const hooks = [];
1812
- let current = state;
1813
- let index = 0;
1814
- while (current && index < MAX_HOOK_DEPTH) {
1815
- hooks.push({
1816
- index,
1817
- memoizedState: safeCloneForHistory(current.memoizedState)
1818
- });
1819
- current = current.next;
1820
- index++;
1821
- }
1822
- return {
1823
- __type: "Hooks",
1824
- hooks
1825
- };
1826
- }
1827
-
1828
- // It's a regular state object (class component or simple state)
1829
- return safeCloneForHistory(state);
1830
- }
1
+ "use strict";import{hookStateChanged}from"./ownStateChanged";const COMPONENT_PROP_CONFIGS={RCTText:{alwaysTrack:["children"],skip:[],description:"Text component - children is the displayed content"},RCTVirtualText:{alwaysTrack:["children"],skip:[],description:"Virtual Text (nested) - children is the displayed content"},RCTView:{alwaysTrack:[],skip:["children"],description:"View container - children are React elements"},RCTImageView:{alwaysTrack:["source"],skip:["children"],description:"Image - source contains the image URL/require"},RCTTextInput:{alwaysTrack:["value","defaultValue"],skip:["children"],description:"TextInput - value is the input content"},RCTSwitch:{alwaysTrack:["value"],skip:["children"],description:"Switch - value is the toggle state"},default:{alwaysTrack:[],skip:["children"],description:"Unknown component type"}};function getComponentPropConfig(e){return e&&COMPONENT_PROP_CONFIGS[e]||COMPONENT_PROP_CONFIGS.default}const componentFiberStates=new WeakMap;function getComponentFiberPrevState(e){if(!e)return;let o=componentFiberStates.get(e);return o||(e.alternate&&(o=componentFiberStates.get(e.alternate),o)?o:void 0)}function setComponentFiberState(e,o){e&&(componentFiberStates.set(e,o),e.alternate&&componentFiberStates.set(e.alternate,o))}const previousStates=new Map,MAX_STORED_STATES=500,MAX_CHANGED_KEYS=10,MAX_HOOK_DEPTH=50,MAX_PARENT_DEPTH=50,INTERNAL_COMPONENT_NAMES=new Set(["View","Text","TextImpl","Image","ScrollView","FlatList","SectionList","TouchableOpacity","TouchableHighlight","TouchableWithoutFeedback","Pressable","TextInput","Switch","ActivityIndicator","Modal","StatusBar","KeyboardAvoidingView","AnimatedComponent","AnimatedComponentWrapper","Fragment","Suspense","Provider","Consumer","Context","ForwardRef","Unknown","Component"]);function isInternalComponentName(e){return!e||!!INTERNAL_COMPONENT_NAMES.has(e)||!!e.startsWith("Animated")}function getOwningComponentFiber(e){if(!e)return null;let o=e._debugOwner||e.return,t=0,n=null;for(;o&&t<30;){const e=o.tag;if((0===e||1===e||11===e||15===e)&&(n||(n=o),!isInternalComponentName(getComponentNameFromFiber(o))))return o;o=o.return,t++}return n}function getComponentNameFromFiber(e){if(!e)return null;const o=e.type;if(o){if("string"==typeof o)return o;if(o.name)return o.name;if(o.displayName)return o.displayName}return null}function detectHookType(e){if(!e||"object"!=typeof e)return"unknown";if(null!==e.queue&&void 0!==e.queue)return"useState";const o=e.memoizedState;if(null!==o&&"object"==typeof o&&!Array.isArray(o)&&"current"in o&&1===Object.keys(o).length)return"useRef";if(Array.isArray(o)&&2===o.length){const[,e]=o;if(Array.isArray(e)||null===e)return"function"==typeof o[0]?"useCallback":"useMemo"}return null!==o&&"object"==typeof o&&!Array.isArray(o)&&("tag"in o||"create"in o||"destroy"in o)?"useEffect":"unknown"}function extractHookValue(e,o){const t=e?.memoizedState;switch(o){case"useState":case"useReducer":default:return t;case"useRef":return t?.current;case"useMemo":case"useCallback":return Array.isArray(t)?t[0]:t;case"useEffect":return"[effect]"}}function extractHookStates(e){if(!e?.memoizedState)return null;const o=e.memoizedState;if("object"!=typeof o||null===o)return null;if(!("next"in o)&&!("queue"in o)&&!("memoizedState"in o))return null;const t=[];let n=o,r=0;for(;null!==n&&r<50;){const e=detectHookType(n),o=extractHookValue(n,e);t.push({index:r,type:e,value:o,rawState:n.memoizedState}),n=n.next,r++}return t.length>0?t:null}function compareHookStates(e,o){if(!e||!o)return null;const t=[],n=Math.max(e.length,o.length);for(let r=0;r<n;r++){const n=e[r],a=o[r];if(n||!a)if(!n||a){if(n&&a){if(n.rawState===a.rawState)continue;if("useEffect"===a.type)continue;if("useMemo"===a.type||"useCallback"===a.type)continue;if("useState"===a.type||"useReducer"===a.type||"useRef"===a.type){const e=formatHookValue(n.value,n.type),o=formatHookValue(a.value,a.type);t.push({index:r,type:a.type,previousValue:e,currentValue:o,description:`${e} → ${o}`})}}}else t.push({index:r,type:n.type,previousValue:formatHookValue(n.value,n.type),description:`Hook[${r}] removed`});else t.push({index:r,type:a.type,currentValue:formatHookValue(a.value,a.type),description:`Hook[${r}] added`})}return t.length>0?t:null}function formatHookValue(e,o){return formatDisplayValue(e)}function formatDisplayValue(e){return null===e?null:void 0!==e?"boolean"==typeof e||"number"==typeof e?e:"string"==typeof e?e.length>30?e.slice(0,30)+"...":e:"function"==typeof e?`[Function: ${e.name||"anonymous"}]`:Array.isArray(e)?`[Array: ${e.length} items]`:"object"==typeof e?`{Object: ${Object.keys(e).length} keys}`:String(e):void 0}function detectComponentCause(e){if(!e)return{cause:"unknown",hookChanges:null};let o=e,t=e.alternate;const n=getComponentFiberPrevState(e);if(n&&t){const r=extractHookStates(e),a=extractHookStates(t),s=n.extractedHooks;if(r&&a&&s&&s.length>0){const n=s.find(e=>"useState"===e.type);if(n){const s=r.find(e=>e.index===n.index),i=a.find(e=>e.index===n.index);if(s&&i){const r=s.value===n.value,a=i.value===n.value;r&&!a&&(o=t,t=e)}}}}const r=extractHookStates(o);let a=null,s=null,i=null;return t?(a=t.memoizedProps,s=t.memoizedState,i=extractHookStates(t)):n&&(a=n.memoizedProps,s=n.memoizedState,i=n.extractedHooks),setComponentFiberState(o,{memoizedProps:o.memoizedProps,memoizedState:o.memoizedState,extractedHooks:r}),null===a?{cause:"mount",hookChanges:null}:shallowEqual(a,o.memoizedProps)?hookStateChanged(s,o.memoizedState,{isClassComponent:1===o.tag})?{cause:"state",hookChanges:compareHookStates(i,r)}:{cause:"parent",hookChanges:null}:{cause:"props",hookChanges:null}}function shallowEqual(e,o){if(e===o)return!0;if(!e||!o)return!1;if("object"!=typeof e||"object"!=typeof o)return!1;const t=Object.keys(e),n=Object.keys(o);if(t.length!==n.length)return!1;for(const n of t)if(e[n]!==o[n])return!1;return!0}function safeStringify(e,o=3){const t=new WeakSet;return function e(n,r){if(r>o)return"[MAX_DEPTH]";if(null===n)return null;if(void 0===n)return;if("function"==typeof n)return`[Function: ${n.name||"anonymous"}]`;if("symbol"==typeof n)return`[Symbol: ${n.toString()}]`;if("object"!=typeof n)return n;if(t.has(n))return"[Circular]";if(t.add(n),Array.isArray(n))return n.slice(0,10).map(o=>e(o,r+1));const a={},s=Object.keys(n).slice(0,20);for(const o of s)try{a[o]=e(n[o],r+1)}catch{a[o]="[Error accessing]"}return a}(e,0)}function logRawFiberData(e,o,t,n,r,a){const s=getComponentNameFromFiber(t)||"Unknown";if(console.log("\n========================================"),console.log("[RN-BUOY DEBUG] RENDER EVENT"),console.log("========================================"),console.log(`Native Tag: ${e}`),console.log(`Component Name: ${s}`),console.log(`Is First Render: ${!n}`),console.log(`Component Cause Detected: ${a}`),console.log("----------------------------------------"),console.log("\n--- NATIVE FIBER (Host Component) ---"),console.log("fiber.type:",o?.type),console.log("fiber.tag:",o?.tag),console.log("fiber.memoizedProps (CURRENT):",safeStringify(o?.memoizedProps)),console.log("fiber.memoizedState:",safeStringify(o?.memoizedState)),n&&(console.log("PREVIOUS memoizedProps:",safeStringify(n.memoizedProps)),console.log("PREVIOUS memoizedState:",safeStringify(n.memoizedState))),console.log("\n--- COMPONENT FIBER (React Component) ---"),t){console.log("componentFiber.type:",t?.type?.name||t?.type),console.log("componentFiber.tag:",t?.tag),console.log("componentFiber.memoizedProps:",safeStringify(t?.memoizedProps)),console.log("componentFiber.memoizedState:",safeStringify(t?.memoizedState)),console.log("\n--- HOOKS STATE (linked list walk) ---");let e=t?.memoizedState,o=0;for(;e&&o<10;)console.log(`Hook[${o}]:`,safeStringify({memoizedState:e.memoizedState,baseState:e.baseState,queue:e.queue?"[Queue object]":null})),e=e.next,o++;console.log("\n--- EXTRACTED HOOKS (Phase 3) ---");const n=extractHookStates(t);if(n)for(const e of n)console.log(`Hook[${e.index}] (${e.type}):`,formatDisplayValue(e.value));else console.log("(No hooks extracted)")}else console.log("(No component fiber found)");console.log("\n--- CHILDREN/TEXT CONTENT ---");const i=o?.memoizedProps?.children;console.log("children type:",typeof i),console.log("children value:",safeStringify(i)),console.log("\n========================================\n")}function getTagName(e){return void 0===e?"undefined":{0:"FunctionComponent",1:"ClassComponent",2:"IndeterminateComponent",3:"HostRoot",4:"HostPortal",5:"HostComponent",6:"HostText",7:"Fragment",8:"Mode",9:"ContextConsumer",10:"ContextProvider",11:"ForwardRef",12:"Profiler",13:"SuspenseComponent",14:"MemoComponent",15:"SimpleMemoComponent"}[e]||`Unknown(${e})`}function logMinimal(e,o){if(o.hookChanges&&0!==o.hookChanges.length)for(const t of o.hookChanges)console.log(`[${e||"Unknown"}] ${t.type}[${t.index}]: ${t.previousValue} → ${t.currentValue}`)}function logVerbose(e,o,t,n,r){const a=getComponentNameFromFiber(t)||"Unknown",s="string"==typeof o?.type?o.type:"Unknown",{cause:i,hookChanges:l}=n;if(console.log(`[RENDER] ${a} (${s}:${e}) | Cause: ${i.toUpperCase()}`+(r&&r.length>0?` | Props: [${r.join(", ")}]`:"")),l&&l.length>0)for(const e of l)console.log(` └─ ${e.type}[${e.index}]: ${e.previousValue} → ${e.currentValue}`)}function logComprehensiveRenderData(e,o,t,n,r,a,s){const i=getComponentNameFromFiber(t)||"Unknown",l="string"==typeof o?.type?o.type:"Unknown";console.log("\n[RN-BUOY RENDER DEBUG] ═══════════════════════════════════════"),console.log(`Render #${s} for ${l} (nativeTag: ${e})`),console.log(`Timestamp: ${(new Date).toISOString()}`),console.log("═══════════════════════════════════════════════════════════════\n"),console.log(`NATIVE FIBER (${l}):`),console.log(` type: "${o?.type}"`),console.log(` tag: ${o?.tag} (${getTagName(o?.tag)})`);const c=o?.alternate,u=c?"alternate":n?"storage":"none";console.log(` Previous values source: ${u}`);const m=o?.memoizedProps?.children,p=c?.memoizedProps?.children,f=void 0!==p?p:n?.memoizedProps?.children,g=void 0!==f&&m!==f;console.log(` memoizedProps.children: ${formatDisplayValue(m)}${g?` (was: ${formatDisplayValue(f)})`:void 0!==f?" (unchanged)":" (first render)"}`);const d=o?.memoizedProps?.style,y=c?.memoizedProps?.style,S=void 0!==y?y:n?.memoizedProps?.style,h=void 0!==S&&d!==S;if(d&&(console.log(` memoizedProps.style: ${JSON.stringify(safeStringify(d,2))}${h?" (REFERENCE CHANGED)":""}`),h&&S)){const e=deepEqual(d,S);console.log(` └─ Values actually changed: ${!e}`)}console.log(` All memoizedProps keys: [${Object.keys(o?.memoizedProps||{}).join(", ")}]`);const C=o?.memoizedProps?.testID,b=o?.memoizedProps?.nativeID,k=o?.memoizedProps?.accessibilityLabel;if(C&&console.log(` testID: "${C}"`),b&&console.log(` nativeID: "${b}"`),k&&console.log(` accessibilityLabel: "${k}"`),console.log(" alternate: "+(c?"YES":"NO")),c&&console.log(` alternate.memoizedProps.children: ${formatDisplayValue(p)}`),console.log(" Tree structure:"),console.log(` return (parent): ${o?.return?getTagName(o.return.tag):"null"}`),console.log(` child: ${o?.child?getTagName(o.child.tag):"null"}`),console.log(` sibling: ${o?.sibling?getTagName(o.sibling.tag):"null"}`),console.log(""),console.log(`COMPONENT FIBER (${i}):`),t){if(console.log(` name: "${i}"`),console.log(` type: ${typeof t.type} (${t.type?.name||t.type?.displayName||"anonymous"})`),console.log(` tag: ${t.tag} (${getTagName(t.tag)})`),console.log(` memoizedProps: ${JSON.stringify(safeStringify(t.memoizedProps,2))}`),console.log(" alternate: "+(t.alternate?"YES":"NO")),t.alternate){console.log(` ALTERNATE memoizedProps: ${JSON.stringify(safeStringify(t.alternate.memoizedProps,2))}`);const e=!shallowEqual(t.alternate.memoizedProps,t.memoizedProps);console.log(" Props changed (vs alternate): "+(e?"YES":"NO")),console.log(" ALTERNATE memoizedState: "+(t.alternate.memoizedState===t.memoizedState?"SAME":"DIFFERENT"))}const e=getComponentFiberPrevState(t);if(e){console.log(` WeakMap PREVIOUS memoizedProps: ${JSON.stringify(safeStringify(e.memoizedProps,2))}`);const o=!shallowEqual(e.memoizedProps,t.memoizedProps);console.log(" Props changed (vs WeakMap): "+(o?"YES":"NO"))}else console.log(" WeakMap PREVIOUS state: (not found - first render or WeakMap cleared)");if(t._debugOwner){const e=getComponentNameFromFiber(t._debugOwner);console.log(` _debugOwner: "${e}"`);let o=t._debugOwner,n=1;for(;o&&n<5;){const e=getComponentNameFromFiber(o);console.log(` └─[${n}] ${e||"unknown"} (tag: ${o.tag})`),o=o._debugOwner,n++}}let n=0,r=o._debugOwner||o.return;for(;r&&r!==t&&n<30;)r=r.return,n++;console.log(` Depth from native fiber: ${n}`)}else console.log(" (No component fiber found - could not walk up tree)");if(console.log(""),console.log("HOOKS:"),t?.memoizedState){const e=extractHookStates(t),o=t.alternate?extractHookStates(t.alternate):null,n=getComponentFiberPrevState(t),r=o||n?.extractedHooks,a=o?"alternate":n?"WeakMap":"none";e&&e.length>0?(console.log(` Total hooks: ${e.length}`),console.log(` Previous values source: ${a}`),e.forEach((e,o)=>{const t=r?.[o],n=!!t&&t.rawState!==e.rawState,a=t?formatDisplayValue(t.value):"N/A",s=n?" ← CHANGED":"";console.log(` [${o}] ${e.type}: ${formatDisplayValue(e.value)}${t?` (was: ${a})`:" (first render)"}${s}`)})):(console.log(" (Could not extract hooks - memoizedState structure not recognized)"),console.log(" Raw memoizedState type: "+typeof t.memoizedState),console.log(` Has 'next' property: ${"next"in(t.memoizedState||{})}`),console.log(` Has 'queue' property: ${"queue"in(t.memoizedState||{})}`))}else console.log(" (No memoizedState - class component, no hooks, or not a function component)");if(console.log(""),console.log("RAW HOOKS DATA:"),t?.memoizedState&&"object"==typeof t.memoizedState){let e=t.memoizedState,o=0;for(;e&&o<10;)console.log(` Hook[${o}]:`),console.log(` memoizedState: ${safeStringify(e.memoizedState,2)}`),console.log(` baseState: ${safeStringify(e.baseState,2)}`),console.log(" queue: "+(e.queue?"[Queue present]":"null")),console.log(" next: "+(e.next?"[Next hook]":"null")),e=e.next,o++;0===o&&console.log(" (memoizedState is not a hooks linked list)")}console.log(""),console.log("DETECTION RESULTS:"),console.log(` Component Cause: ${r.cause.toUpperCase()}`),r.hookChanges&&r.hookChanges.length>0&&(console.log(" Hook Changes Detected:"),r.hookChanges.forEach(e=>{console.log(` [${e.index}] ${e.type}: ${e.previousValue} → ${e.currentValue}`),e.description&&console.log(` ${e.description}`)}));const N="string"==typeof o?.type?o.type:void 0,z=getChangedKeys(n?.memoizedProps,o?.memoizedProps,N);z&&z.length>0?console.log(` Native Props Changed: [${z.join(", ")}]`):n&&console.log(" Native Props Changed: (none detected)"),console.log(""),console.log("BATCH CONTEXT:"),console.log(` Batch size: ${a.size}`),console.log(` All tags in batch: [${Array.from(a).slice(0,20).join(", ")}${a.size>20?"...":""}]`);const $=getParentNativeTag(o),P=null!==$&&a.has($);if(console.log(` Parent nativeTag: ${$??"not found"}`),console.log(" Parent in batch: "+(P?"YES":"NO")),a.size>1){console.log(" Components in same batch:");let e=o?.return,t=0;for(;e&&t<10;){const o=getNativeTagFromStateNode(e.stateNode);if(null!==o&&a.has(o)){const n=getComponentNameFromFiber(e)||e.type;console.log(` [depth ${t}] ${n} (tag: ${o})`)}e=e.return,t++}}console.log("\n═══════════════════════════════════════════════════════════════\n")}export function detectRenderCause(e,o,t,n="off"){const r=Date.now();if(!o)return{type:"unknown",timestamp:r};let a=o,s=o.alternate;const i=previousStates.get(e);if(i&&s){const e=i.memoizedProps?.children,t=o.memoizedProps?.children,r=s.memoizedProps?.children,l=o.memoizedProps===i.memoizedProps||void 0!==e&&t===e,c=s.memoizedProps===i.memoizedProps||void 0!==e&&r===e;l&&!c&&(a=s,s=o,"all"===n&&console.log("[RenderCause] Detected fiber swap - using alternate as current"))}let l=null,c=null;s?(l=s.memoizedProps,c=s.memoizedState):i&&(l=i.memoizedProps,c=i.memoizedState);const u=null!==l?{memoizedProps:l,memoizedState:c,timestamp:r}:void 0,m={memoizedProps:a.memoizedProps,memoizedState:a.memoizedState,timestamp:r};updateStoredState(e,m);const p=getOwningComponentFiber(a),f=getComponentNameFromFiber(p)||void 0,g=getParentComponentName(a,f)||void 0,d=detectComponentCause(p),y=d.cause,S=d.hookChanges;if(!u){if("off"!==n)if("minimal"===n)console.log(`[${f||"Unknown"}] MOUNT`);else if("verbose"===n){const o="string"==typeof a?.type?a.type:"Unknown";console.log(`[RENDER] ${f||"Unknown"} (${o}:${e}) | Cause: MOUNT`)}else"all"===n&&logComprehensiveRenderData(e,a,p,u,d,t,previousStates.has(e)?previousStates.size:1);return{type:"mount",timestamp:r,componentCause:"mount",componentName:f}}const h="string"==typeof a.type?a.type:void 0,C=getChangedKeys(u.memoizedProps,m.memoizedProps,h);if("off"!==n&&("minimal"===n?logMinimal(f,d):"verbose"===n?logVerbose(e,a,p,d,C):"all"===n&&logComprehensiveRenderData(e,a,p,u,d,t,previousStates.has(e)?previousStates.size:1)),C&&C.length>0)return{type:"props",changedKeys:C.slice(0,10),timestamp:r,componentCause:y,componentName:f,parentComponentName:g,hookChanges:S||void 0};const b=detectHookChanges(u.memoizedState,m.memoizedState);if(b&&b.length>0)return{type:"hooks",hookIndices:b,timestamp:r,componentCause:y,componentName:f,parentComponentName:g,hookChanges:S||void 0};const k=getParentNativeTag(a);return k&&t.has(k)?{type:"parent",timestamp:r,componentCause:y,componentName:f,parentComponentName:g,hookChanges:S||void 0}:"state"===y?{type:"state",timestamp:r,componentCause:y,componentName:f,parentComponentName:g,hookChanges:S||void 0}:"props"===y?{type:"props",timestamp:r,componentCause:y,componentName:f,parentComponentName:g}:"parent"===y?{type:"parent",timestamp:r,componentCause:y,componentName:f,parentComponentName:g}:{type:"unknown",timestamp:r,componentCause:y,componentName:f,parentComponentName:g}}function getChangedKeys(e,o,t){if(e===o)return null;if(!e||!o)return null;if("object"!=typeof e||"object"!=typeof o)return null;if(Array.isArray(e)||Array.isArray(o))return null;const n=getComponentPropConfig(t),r=new Set([...Object.keys(e),...Object.keys(o)]),a=[];for(const t of r)if(!t.startsWith("__")&&!n.skip.includes(t)){if("children"===t){if(!n.alwaysTrack.includes("children"))continue;const r=e[t],s=o[t];r!==s&&(isPrimitive(r)&&isPrimitive(s)?a.push(`children: ${formatValue(r)} → ${formatValue(s)}`):a.push("children (content)"));continue}if(e[t]!==o[t]){const n=e[t],r=o[t];"style"===t?deepEqual(n,r)?a.push(`${t} (ref only)`):a.push(t):"function"==typeof n&&"function"==typeof r?a.push(`${t} (fn ref)`):a.push(t)}}return a.length>0?a:null}function isPlainObject(e){if(null===e||"object"!=typeof e)return!1;if(Array.isArray(e))return!1;const o=Object.getPrototypeOf(e);return o===Object.prototype||null===o}function deepEqual(e,o,t=0){if(t>5)return!1;if(e===o)return!0;if(typeof e!=typeof o)return!1;if("object"!=typeof e||null===e||null===o)return e===o;if(Array.isArray(e)!==Array.isArray(o))return!1;if(Array.isArray(e)){if(e.length!==o.length)return!1;for(let n=0;n<e.length;n++)if(!deepEqual(e[n],o[n],t+1))return!1;return!0}const n=Object.keys(e),r=Object.keys(o);if(n.length!==r.length)return!1;for(const a of n){if(!r.includes(a))return!1;if(!deepEqual(e[a],o[a],t+1))return!1}return!0}function isPrimitive(e){return null==e||"string"==typeof e||"number"==typeof e||"boolean"==typeof e}function formatValue(e){return null===e?"null":void 0===e?"undefined":"string"==typeof e?e.length>20?`"${e.slice(0,20)}..."`:`"${e}"`:"number"==typeof e||"boolean"==typeof e?String(e):"[object]"}function detectHookChanges(e,o){if(e===o)return null;if(null==e||null==o)return null;if("object"!=typeof e||"object"!=typeof o)return null;const t=[];let n=e,r=o,a=0;for(;null!==r&&a<50;)(null===n||didHookChange(n,r))&&t.push(a),r=r?.next??null,n=n?.next??null,a++;return t.length>0?t:null}function didHookChange(e,o){return e!==o&&(e.memoizedState!==o.memoizedState||void 0!==e.baseState&&e.baseState!==o.baseState)}function getParentNativeTag(e){let o=e?.return,t=0;for(;o&&t<50;){if(o.stateNode){const e=getNativeTagFromStateNode(o.stateNode);if(null!=e)return e}o=o.return,t++}return null}function getParentComponentName(e,o){if(!e)return null;const t=getOwningComponentFiber(e);if(!t)return null;let n=t.return,r=0;for(;n&&r<50;){const e=n.tag;if(0===e||1===e||11===e||15===e){const e=getComponentNameFromFiber(n);if(e&&!isInternalComponentName(e)&&e!==o)return e}n=n.return,r++}return null}function getNativeTagFromStateNode(e){return e?"number"==typeof e.__nativeTag?e.__nativeTag:"number"==typeof e._nativeTag?e._nativeTag:"number"==typeof e.canonical?.__nativeTag?e.canonical.__nativeTag:"number"==typeof e.canonical?.publicInstance?.__nativeTag?e.canonical.publicInstance.__nativeTag:null:null}function updateStoredState(e,o){if(previousStates.size>=500){const e=Array.from(previousStates.entries());e.sort((e,o)=>e[1].timestamp-o[1].timestamp);const o=Math.floor(125);for(let t=0;t<o;t++)previousStates.delete(e[t][0])}previousStates.set(e,o)}export function clearRenderCauseState(){previousStates.clear()}export function removeRenderCauseState(e){previousStates.delete(e)}export function getRenderCauseStats(){return{storedStates:previousStates.size,maxStates:500}}const MAX_CLONE_DEPTH=5,MAX_STRING_LENGTH=200,MAX_ARRAY_ITEMS=20,MAX_OBJECT_KEYS=30;export function safeCloneForHistory(e,o=0,t=new WeakSet){if(null==e)return e;if("boolean"==typeof e||"number"==typeof e)return e;if("string"==typeof e)return e.length>200?e.slice(0,200)+"...":e;if("function"==typeof e)return`[Function: ${e.name||"anonymous"}]`;if("symbol"==typeof e)return`[Symbol: ${e.description||""}]`;if(o>=5)return Array.isArray(e)?`[Array: ${e.length} items]`:"object"==typeof e?`[Object: ${Object.keys(e).length} keys]`:"[...]";if("object"==typeof e){if(t.has(e))return"[Circular]";t.add(e)}if(Array.isArray(e)){const n=e.slice(0,20).map(e=>safeCloneForHistory(e,o+1,t));return e.length>20&&n.push(`[...${e.length-20} more]`),n}if(e instanceof Date)return e.toISOString();if(e instanceof Error)return`[Error: ${e.message}]`;if(e instanceof RegExp)return e.toString();if(e instanceof Map){const n={__type:"Map",__size:e.size};let r=0;for(const[a,s]of e){if(r>=30){n[`...${e.size-r} more`]=!0;break}n["string"==typeof a?a:String(a)]=safeCloneForHistory(s,o+1,t),r++}return n}if(e instanceof Set)return{__type:"Set",__size:e.size,values:Array.from(e).slice(0,20).map(e=>safeCloneForHistory(e,o+1,t))};if("object"==typeof e){const n={},r=Object.keys(e),a=r.slice(0,30);for(const r of a)if(!r.startsWith("__")&&"children"!==r)try{n[r]=safeCloneForHistory(e[r],o+1,t)}catch{n[r]="[Error accessing property]"}return r.length>30&&(n[`...${r.length-30} more keys`]=!0),n}return"[Unknown type]"}export function capturePropsSnapshot(e){if(e?.memoizedProps)return safeCloneForHistory(e.memoizedProps)}export function captureStateSnapshot(e){if(!e?.memoizedState)return;const o=e.memoizedState;if("object"==typeof o&&null!==o&&"next"in o){const e=[];let t=o,n=0;for(;t&&n<50;)e.push({index:n,memoizedState:safeCloneForHistory(t.memoizedState)}),t=t.next,n++;return{__type:"Hooks",hooks:e}}return safeCloneForHistory(o)}