bippy 0.6.1-dev.0597fd5 → 0.6.1-dev.3a24abf

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 (55) hide show
  1. package/README.md +26 -48
  2. package/dist/core.cjs +1 -1
  3. package/dist/core.d.cts +32 -44
  4. package/dist/core.d.ts +32 -44
  5. package/dist/core.js +1 -1
  6. package/dist/core2.cjs +9 -0
  7. package/dist/core2.d.cts +3 -3
  8. package/dist/core2.d.ts +3 -3
  9. package/dist/core2.js +9 -0
  10. package/dist/index.cjs +1 -1
  11. package/dist/index.d.cts +3 -3
  12. package/dist/index.d.ts +3 -3
  13. package/dist/index.iife.js +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/install-hook-only.cjs +1 -1
  16. package/dist/install-hook-only.d.cts +1 -1
  17. package/dist/install-hook-only.d.ts +1 -1
  18. package/dist/install-hook-only.iife.js +1 -1
  19. package/dist/install-hook-only.js +1 -1
  20. package/dist/rdt-hook.cjs +1 -1
  21. package/dist/rdt-hook.js +1 -1
  22. package/dist/source.cjs +14 -4
  23. package/dist/source.d.cts +1 -1
  24. package/dist/source.d.ts +1 -1
  25. package/dist/source.js +14 -4
  26. package/dist/types.d.cts +291 -0
  27. package/dist/types.d.ts +291 -0
  28. package/package.json +14 -12
  29. package/src/core.ts +206 -155
  30. package/src/generated/react-work-tags.ts +169 -0
  31. package/src/rdt-hook.ts +24 -67
  32. package/src/react-internals.ts +67 -0
  33. package/src/source/constants.ts +1 -1
  34. package/src/source/get-source.ts +2 -1
  35. package/src/source/owner-stack.ts +64 -77
  36. package/src/source/parse-stack.ts +1 -1
  37. package/src/source/symbolication.ts +25 -4
  38. package/src/types.ts +154 -333
  39. package/src/unsubscribe.ts +2 -2
  40. package/dist/get-source.cjs +0 -19
  41. package/dist/get-source.js +0 -19
  42. package/dist/react-refresh.cjs +0 -9
  43. package/dist/react-refresh.d.cts +0 -66
  44. package/dist/react-refresh.d.ts +0 -66
  45. package/dist/react-refresh.js +0 -9
  46. package/dist/unsubscribe.d.cts +0 -298
  47. package/dist/unsubscribe.d.ts +0 -298
  48. package/src/react-refresh/constants.ts +0 -9
  49. package/src/react-refresh/detect-hmr-transport.ts +0 -33
  50. package/src/react-refresh/index.ts +0 -173
  51. package/src/react-refresh/metro-hmr-transport.ts +0 -188
  52. package/src/react-refresh/next-webpack-hmr-transport.ts +0 -72
  53. package/src/react-refresh/normalize-hmr-file-path.ts +0 -24
  54. package/src/react-refresh/types.ts +0 -7
  55. package/src/react-refresh/vite-hmr-transport.ts +0 -116
package/src/core.ts CHANGED
@@ -1,5 +1,4 @@
1
- // [!!!] IMPORTANT: do not import React in this file
2
- // since it will be executed before the react devtools hook is created
1
+ // React must remain a type-only import because this module loads immediately after the DevTools hook.
3
2
 
4
3
  import type * as React from "react";
5
4
 
@@ -7,6 +6,7 @@ import type {
7
6
  ContextDependency,
8
7
  Fiber,
9
8
  FiberRoot,
9
+ HostFiber,
10
10
  MemoizedState,
11
11
  ReactDevToolsGlobalHook,
12
12
  ReactRenderer,
@@ -17,55 +17,101 @@ import {
17
17
  BIPPY_INSTRUMENTATION_STRING,
18
18
  getRDTHook,
19
19
  hasRDTHook,
20
- isReactRefresh,
21
20
  isRealReactDevtools,
22
21
  onRendererInject,
23
22
  } from "./rdt-hook.js";
23
+ import {
24
+ ClassComponentTag,
25
+ CONCURRENT_MODE_NUMBER,
26
+ CONCURRENT_MODE_SYMBOL_DESCRIPTION,
27
+ CONCURRENT_MODE_SYMBOL_STRING,
28
+ ContextConsumerTag,
29
+ DEPRECATED_ASYNC_MODE_SYMBOL_DESCRIPTION,
30
+ DEPRECATED_ASYNC_MODE_SYMBOL_STRING,
31
+ DehydratedSuspenseComponentTag,
32
+ ELEMENT_TYPE_SYMBOL_STRING,
33
+ ForwardRefTag,
34
+ FragmentTag,
35
+ FunctionComponentTag,
36
+ HostComponentTag,
37
+ HostHoistableTag,
38
+ HostRootTag,
39
+ HostSingletonTag,
40
+ HostTextTag,
41
+ LegacyHiddenComponentTag,
42
+ MemoComponentTag,
43
+ MutationMask,
44
+ OffscreenComponentTag,
45
+ ReactBuildType,
46
+ ReactFiberFlags,
47
+ SimpleMemoComponentTag,
48
+ SuspenseComponentTag,
49
+ TRANSITIONAL_ELEMENT_TYPE_SYMBOL_STRING,
50
+ } from "./react-internals.js";
24
51
  import { toUnsubscribe, type Unsubscribe } from "./unsubscribe.js";
25
52
 
26
- // https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactWorkTags.js
27
- export const FunctionComponentTag = 0;
28
- export const ClassComponentTag = 1;
29
- export const HostRootTag = 3;
30
- export const HostPortalTag = 4;
31
- export const HostComponentTag = 5;
32
- export const HostTextTag = 6;
33
- export const FragmentTag = 7;
34
- export const ContextConsumerTag = 9;
35
- export const ForwardRefTag = 11;
36
- export const SuspenseComponentTag = 13;
37
- export const MemoComponentTag = 14;
38
- export const SimpleMemoComponentTag = 15;
39
- export const LazyComponentTag = 16;
40
- export const DehydratedSuspenseComponentTag = 18;
41
- export const SuspenseListComponentTag = 19;
42
- export const OffscreenComponentTag = 22;
43
- export const LegacyHiddenComponentTag = 23;
44
- export const HostHoistableTag = 26;
45
- export const HostSingletonTag = 27;
46
- export const ActivityComponentTag = 28;
47
- export const ViewTransitionComponentTag = 30;
48
-
49
- export const CONCURRENT_MODE_NUMBER = 0xeacf;
50
- export const ELEMENT_TYPE_SYMBOL_STRING = "Symbol(react.element)";
51
- export const TRANSITIONAL_ELEMENT_TYPE_SYMBOL_STRING = "Symbol(react.transitional.element)";
52
- export const CONCURRENT_MODE_SYMBOL_STRING = "Symbol(react.concurrent_mode)";
53
- export const DEPRECATED_ASYNC_MODE_SYMBOL_STRING = "Symbol(react.async_mode)";
54
- export const CONCURRENT_MODE_SYMBOL_DESCRIPTION = "react.concurrent_mode";
55
- export const DEPRECATED_ASYNC_MODE_SYMBOL_DESCRIPTION = "react.async_mode";
56
-
57
- // https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberFlags.js
58
- const PerformedWork = 0b1;
59
- const Placement = 0b10;
60
- const Hydrating = 0b1000000000000;
61
- const Update = 0b100;
62
- const Cloned = 0b1000;
63
- const ChildDeletion = 0b10000;
64
- const ContentReset = 0b100000;
65
- const Snapshot = 0b10000000000;
66
- const Visibility = 0b10000000000000;
67
- const MutationMask =
68
- Placement | Update | ChildDeletion | ContentReset | Hydrating | Visibility | Snapshot;
53
+ export {
54
+ ActivityComponentTag,
55
+ ClassComponentTag,
56
+ CONCURRENT_MODE_NUMBER,
57
+ CONCURRENT_MODE_SYMBOL_DESCRIPTION,
58
+ CONCURRENT_MODE_SYMBOL_STRING,
59
+ ContextConsumerTag,
60
+ DEPRECATED_ASYNC_MODE_SYMBOL_DESCRIPTION,
61
+ DEPRECATED_ASYNC_MODE_SYMBOL_STRING,
62
+ DehydratedSuspenseComponentTag,
63
+ ELEMENT_TYPE_SYMBOL_STRING,
64
+ ForwardRefTag,
65
+ FragmentTag,
66
+ FunctionComponentTag,
67
+ HostComponentTag,
68
+ HostHoistableTag,
69
+ HostPortalTag,
70
+ HostRootTag,
71
+ HostSingletonTag,
72
+ HostTextTag,
73
+ LazyComponentTag,
74
+ LegacyHiddenComponentTag,
75
+ MemoComponentTag,
76
+ OffscreenComponentTag,
77
+ SimpleMemoComponentTag,
78
+ SuspenseComponentTag,
79
+ SuspenseListComponentTag,
80
+ TRANSITIONAL_ELEMENT_TYPE_SYMBOL_STRING,
81
+ ViewTransitionComponentTag,
82
+ } from "./react-internals.js";
83
+
84
+ const { Cloned, PerformedWork } = ReactFiberFlags;
85
+
86
+ const isObjectRecord = (value: unknown): value is Record<string, unknown> =>
87
+ typeof value === "object" && value !== null;
88
+
89
+ const isComponentType = (value: unknown): value is React.ComponentType<unknown> =>
90
+ typeof value === "function";
91
+
92
+ const getPropertyValue = (value: object, key: PropertyKey): unknown => {
93
+ let currentValue: object | null = value;
94
+ while (currentValue) {
95
+ const descriptor = Object.getOwnPropertyDescriptor(currentValue, key);
96
+ if (descriptor) {
97
+ if ("value" in descriptor) return descriptor.value;
98
+ return descriptor.get?.call(value);
99
+ }
100
+ currentValue = Object.getPrototypeOf(currentValue);
101
+ }
102
+ return undefined;
103
+ };
104
+
105
+ const getTypeName = (value: object): string | null => {
106
+ const displayName = getPropertyValue(value, "displayName");
107
+ if (typeof displayName === "string" && displayName) return displayName;
108
+ const name = getPropertyValue(value, "name");
109
+ return typeof name === "string" && name ? name : null;
110
+ };
111
+
112
+ interface FiberSelector {
113
+ (node: Fiber): boolean | Promise<boolean | void> | void;
114
+ }
69
115
 
70
116
  /**
71
117
  * Returns `true` if object is a React Element.
@@ -74,9 +120,8 @@ const MutationMask =
74
120
  */
75
121
  export const isValidElement = (element: unknown): element is React.ReactElement =>
76
122
  typeof element === "object" &&
77
- element != null &&
123
+ element !== null &&
78
124
  "$$typeof" in element &&
79
- // react 18 uses Symbol.for('react.element'), react 19 uses Symbol.for('react.transitional.element')
80
125
  [ELEMENT_TYPE_SYMBOL_STRING, TRANSITIONAL_ELEMENT_TYPE_SYMBOL_STRING].includes(
81
126
  String(element.$$typeof),
82
127
  );
@@ -86,7 +131,7 @@ export const isValidElement = (element: unknown): element is React.ReactElement
86
131
  */
87
132
  export const isValidFiber = (fiber: unknown): fiber is Fiber =>
88
133
  typeof fiber === "object" &&
89
- fiber != null &&
134
+ fiber !== null &&
90
135
  "tag" in fiber &&
91
136
  "stateNode" in fiber &&
92
137
  "return" in fiber &&
@@ -99,16 +144,15 @@ export const isValidFiber = (fiber: unknown): fiber is Fiber =>
99
144
  *
100
145
  * @see https://reactnative.dev/architecture/glossary#host-view-tree-and-host-view
101
146
  */
102
- export const isHostFiber = (fiber: Fiber): boolean => {
147
+ export const isHostFiber = (fiber: Fiber): fiber is HostFiber => {
103
148
  switch (fiber.tag) {
104
149
  case HostComponentTag:
105
- // @ts-expect-error: it exists
150
+ case HostTextTag:
106
151
  case HostHoistableTag:
107
- // @ts-expect-error: it exists
108
152
  case HostSingletonTag:
109
153
  return true;
110
154
  default:
111
- return typeof fiber.type === "string";
155
+ return false;
112
156
  }
113
157
  };
114
158
 
@@ -230,7 +274,7 @@ export const traverseProps = (
230
274
  export const didFiberRender = (fiber: Fiber): boolean => {
231
275
  const nextProps = fiber.memoizedProps;
232
276
  const prevProps = fiber.alternate?.memoizedProps || {};
233
- const flags = fiber.flags ?? (fiber as unknown as { effectTag: number }).effectTag ?? 0;
277
+ const flags = fiber.flags ?? fiber.effectTag ?? 0;
234
278
 
235
279
  switch (fiber.tag) {
236
280
  case ClassComponentTag:
@@ -297,7 +341,8 @@ export const getFiberStack = (fiber: Fiber): Fiber[] => {
297
341
  let currentFiber = fiber;
298
342
  while (currentFiber.return) {
299
343
  stack.push(currentFiber);
300
- currentFiber = currentFiber.return as Fiber;
344
+ const parentFiber = currentFiber.return;
345
+ currentFiber = parentFiber;
301
346
  }
302
347
  return stack;
303
348
  };
@@ -405,7 +450,7 @@ export function traverseFiber(
405
450
  ): Promise<Fiber | null>;
406
451
  export function traverseFiber(
407
452
  fiber: Fiber | null,
408
- selector: (node: Fiber) => boolean | Promise<boolean | void> | void,
453
+ selector: FiberSelector,
409
454
  ascending = false,
410
455
  ): Fiber | null | Promise<Fiber | null> {
411
456
  if (!fiber) return null;
@@ -417,11 +462,7 @@ export function traverseFiber(
417
462
 
418
463
  let child = ascending ? fiber.return : fiber.child;
419
464
  while (child) {
420
- const match = await traverseFiberAsync(
421
- child,
422
- selector as (node: Fiber) => Promise<boolean | void>,
423
- ascending,
424
- );
465
+ const match = await traverseFiberAsync(child, selector, ascending);
425
466
  if (match) return match;
426
467
  child = ascending ? null : child.sibling;
427
468
  }
@@ -433,7 +474,7 @@ export function traverseFiber(
433
474
 
434
475
  let child = ascending ? fiber.return : fiber.child;
435
476
  while (child) {
436
- const match = traverseFiberSync(child, selector as (node: Fiber) => boolean | void, ascending);
477
+ const match = traverseFiberSync(child, selector, ascending);
437
478
  if (match) return match;
438
479
  child = ascending ? null : child.sibling;
439
480
  }
@@ -442,11 +483,13 @@ export function traverseFiber(
442
483
 
443
484
  const traverseFiberSync = (
444
485
  fiber: Fiber | null,
445
- selector: (node: Fiber) => boolean | void,
486
+ selector: FiberSelector,
446
487
  ascending = false,
447
488
  ): Fiber | null => {
448
489
  if (!fiber) return null;
449
- if (selector(fiber) === true) return fiber;
490
+ const selection = selector(fiber);
491
+ if (selection instanceof Promise) return null;
492
+ if (selection === true) return fiber;
450
493
 
451
494
  let child = ascending ? fiber.return : fiber.child;
452
495
  while (child) {
@@ -460,7 +503,7 @@ const traverseFiberSync = (
460
503
 
461
504
  const traverseFiberAsync = async (
462
505
  fiber: Fiber | null,
463
- selector: (node: Fiber) => Promise<boolean | void>,
506
+ selector: FiberSelector,
464
507
  ascending = false,
465
508
  ): Promise<Fiber | null> => {
466
509
  if (!fiber) return null;
@@ -490,7 +533,7 @@ export const getTimings = (fiber?: Fiber | null): { selfTime: number; totalTime:
490
533
  let selfTime = totalTime;
491
534
  // TODO: calculate a DOM time, which is just host component summed up
492
535
  let child = fiber?.child ?? null;
493
- while (totalTime > 0 && child != null) {
536
+ while (totalTime > 0 && child !== null) {
494
537
  selfTime -= child.actualDuration ?? 0;
495
538
  child = child.sibling;
496
539
  }
@@ -501,48 +544,29 @@ export const getTimings = (fiber?: Fiber | null): { selfTime: number; totalTime:
501
544
  * Returns `true` if the {@link Fiber} uses React Compiler's memo cache.
502
545
  */
503
546
  export const hasMemoCache = (fiber: Fiber): boolean => {
504
- return Boolean((fiber.updateQueue as unknown as { memoCache: unknown })?.memoCache);
547
+ return Boolean(fiber.updateQueue?.memoCache);
505
548
  };
506
549
 
507
- type FiberType =
508
- | React.ComponentType<unknown>
509
- | React.ForwardRefExoticComponent<unknown>
510
- | React.MemoExoticComponent<React.ComponentType<unknown>>;
511
-
512
550
  /**
513
551
  * Returns the type (e.g. component definition) of the {@link Fiber}
514
552
  */
515
553
  export const getType = (type: unknown): null | React.ComponentType<unknown> => {
516
- const currentType = type as FiberType;
517
- if (typeof currentType === "function") {
518
- return currentType;
519
- }
520
- if (typeof currentType === "object" && currentType) {
521
- // memo / forwardRef case
522
- return getType(
523
- (currentType as React.MemoExoticComponent<React.ComponentType<unknown>>).type ||
524
- (currentType as { render: React.ComponentType<unknown> }).render,
525
- );
526
- }
527
- return null;
554
+ if (isComponentType(type)) return type;
555
+ if (!isObjectRecord(type)) return null;
556
+ return getType(type.type ?? type.render);
528
557
  };
529
558
 
530
559
  /**
531
560
  * Returns the display name of the {@link Fiber} type.
532
561
  */
533
562
  export const getDisplayName = (type: unknown): null | string => {
534
- const currentType = type as FiberType;
535
- if (typeof currentType === "string") {
536
- return currentType;
537
- }
538
- if (typeof currentType !== "function" && !(typeof currentType === "object" && currentType)) {
539
- return null;
540
- }
541
- const name = currentType.displayName || currentType.name || null;
563
+ if (typeof type === "string") return type;
564
+ if ((typeof type !== "function" && typeof type !== "object") || type === null) return null;
565
+ const name = getTypeName(type);
542
566
  if (name) return name;
543
- const unwrappedType = getType(currentType);
567
+ const unwrappedType = getType(type);
544
568
  if (!unwrappedType) return null;
545
- return unwrappedType.displayName || unwrappedType.name || null;
569
+ return getTypeName(unwrappedType);
546
570
  };
547
571
 
548
572
  /**
@@ -550,7 +574,10 @@ export const getDisplayName = (type: unknown): null | string => {
550
574
  */
551
575
  export const detectReactBuildType = (renderer: ReactRenderer): "development" | "production" => {
552
576
  try {
553
- if (typeof renderer.version === "string" && renderer.bundleType > 0) {
577
+ if (
578
+ typeof renderer.version === "string" &&
579
+ renderer.bundleType === ReactBuildType.Development
580
+ ) {
554
581
  return "development";
555
582
  }
556
583
  } catch {}
@@ -562,11 +589,7 @@ export const detectReactBuildType = (renderer: ReactRenderer): "development" | "
562
589
  */
563
590
  export const isInstrumentationActive = (): boolean => {
564
591
  const rdtHook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
565
- return (
566
- Boolean(rdtHook?._instrumentationIsActive) ||
567
- isRealReactDevtools(rdtHook) ||
568
- isReactRefresh(rdtHook)
569
- );
592
+ return Boolean(rdtHook?._instrumentationIsActive) || isRealReactDevtools(rdtHook);
570
593
  };
571
594
 
572
595
  export const _fiberRoots = new Set<FiberRoot>();
@@ -600,9 +623,7 @@ export const setFiberId = (fiber: Fiber, id: number = fiberId++): void => {
600
623
  fiberIdMap.set(fiber, id);
601
624
  };
602
625
 
603
- // react fibers are double buffered, so the alternate fiber may
604
- // be switched to the current fiber and vice versa.
605
- // fiber === fiber.alternate.alternate
626
+ // Fiber alternates share an identity across double-buffered commits.
606
627
  export const getFiberId = (fiber: Fiber): number => {
607
628
  let id = fiberIdMap.get(fiber);
608
629
  if (id === undefined && fiber.alternate) {
@@ -622,7 +643,7 @@ const mountFiberRecursively = (
622
643
  ): void => {
623
644
  let fiber: Fiber | null = firstChild;
624
645
 
625
- while (fiber != null) {
646
+ while (fiber !== null) {
626
647
  if (!fiberIdMap.has(fiber)) {
627
648
  getFiberId(fiber);
628
649
  }
@@ -646,18 +667,12 @@ const mountFiberRecursively = (
646
667
  }
647
668
  }
648
669
  } else {
649
- let primaryChild: Fiber | null = null;
650
- const areSuspenseChildrenConditionallyWrapped = (OffscreenComponentTag as number) === -1;
651
- if (areSuspenseChildrenConditionallyWrapped) {
652
- primaryChild = fiber.child;
653
- } else if (fiber.child !== null) {
654
- primaryChild = fiber.child.child;
655
- }
670
+ const primaryChild = fiber.child?.child ?? null;
656
671
  if (primaryChild !== null) {
657
672
  mountFiberRecursively(onRender, primaryChild, false);
658
673
  }
659
674
  }
660
- } else if (fiber.child != null) {
675
+ } else if (fiber.child !== null) {
661
676
  mountFiberRecursively(onRender, fiber.child, true);
662
677
  }
663
678
  fiber = traverseSiblings ? fiber.sibling : null;
@@ -831,13 +846,15 @@ export const traverseRenderedFibers = (root: FiberRoot, onRender: RenderHandler)
831
846
  } else if (prevFiber !== null) {
832
847
  const wasMounted =
833
848
  prevFiber &&
834
- prevFiber.memoizedState != null &&
835
- prevFiber.memoizedState.element != null &&
849
+ prevFiber.memoizedState !== null &&
850
+ prevFiber.memoizedState.element !== null &&
851
+ prevFiber.memoizedState.element !== undefined &&
836
852
  // A dehydrated root is not considered mounted
837
853
  prevFiber.memoizedState.isDehydrated !== true;
838
854
  const isMounted =
839
- fiber.memoizedState != null &&
840
- fiber.memoizedState.element != null &&
855
+ fiber.memoizedState !== null &&
856
+ fiber.memoizedState.element !== null &&
857
+ fiber.memoizedState.element !== undefined &&
841
858
  // A dehydrated root is not considered mounted
842
859
  fiber.memoizedState.isDehydrated !== true;
843
860
 
@@ -859,16 +876,16 @@ const overrideRenderers = new Set<ReactRenderer>();
859
876
  let areOverrideRenderersWired = false;
860
877
 
861
878
  const wireOverrideRenderers = (): void => {
862
- if (areOverrideRenderersWired) return;
863
879
  if (!hasRDTHook()) return;
864
880
  const rdtHook = getRDTHook();
865
881
  if (!rdtHook?.renderers) return;
866
- areOverrideRenderersWired = true;
867
882
 
868
883
  ensureHookDispatchesToListeners(rdtHook);
869
884
  for (const renderer of rdtHook.renderers.values()) {
870
885
  overrideRenderers.add(renderer);
871
886
  }
887
+ if (areOverrideRenderersWired) return;
888
+ areOverrideRenderersWired = true;
872
889
  onRendererInject((renderer) => {
873
890
  overrideRenderers.add(renderer);
874
891
  });
@@ -918,10 +935,9 @@ const getHookStateDispatch = (
918
935
  hookState = hookState.next;
919
936
  }
920
937
  const queue = hookState?.queue;
921
- if (isPOJO(queue) && typeof queue.dispatch === "function") {
922
- return queue.dispatch as (value: unknown) => void;
923
- }
924
- return null;
938
+ if (!isPOJO(queue)) return null;
939
+ const dispatch = queue.dispatch;
940
+ return typeof dispatch === "function" ? (value) => dispatch(value) : null;
925
941
  };
926
942
 
927
943
  const findContextProviderFiber = (fiber: Fiber, contextType: unknown): Fiber | null => {
@@ -985,11 +1001,10 @@ export const overrideHookState = (fiber: Fiber, id: number, partialValue: unknow
985
1001
  const writes = buildValueWrites(partialValue);
986
1002
 
987
1003
  if (renderers.length > 0) {
988
- const hookId = String(id);
989
1004
  for (const renderer of renderers) {
990
1005
  for (const { path, value } of writes) {
991
1006
  try {
992
- renderer.overrideHookState?.(fiber, hookId, path, value);
1007
+ renderer.overrideHookState?.(fiber, id, path, value);
993
1008
  } catch {}
994
1009
  }
995
1010
  }
@@ -1022,7 +1037,12 @@ export const overrideContext = (fiber: Fiber, contextType: unknown, partialValue
1022
1037
  export interface InstrumentationOptions {
1023
1038
  name?: string;
1024
1039
  onActive?: () => unknown;
1025
- onCommitFiberRoot?: (rendererID: number, root: FiberRoot, priority: number | void) => unknown;
1040
+ onCommitFiberRoot?: (
1041
+ rendererID: number,
1042
+ root: FiberRoot,
1043
+ priority: number | void,
1044
+ didError?: boolean,
1045
+ ) => unknown;
1026
1046
  onCommitFiberUnmount?: (rendererID: number, fiber: Fiber) => unknown;
1027
1047
  onPostCommitFiberRoot?: (rendererID: number, root: FiberRoot) => unknown;
1028
1048
  onScheduleFiberRoot?: (rendererID: number, root: FiberRoot, children: React.ReactNode) => unknown;
@@ -1070,13 +1090,24 @@ const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void
1070
1090
  rendererID,
1071
1091
  root,
1072
1092
  priority,
1093
+ didError,
1073
1094
  ) => {
1074
- prevOnCommitFiberRoot?.(rendererID, root, priority);
1095
+ prevOnCommitFiberRoot?.(rendererID, root, priority, didError);
1075
1096
  if (hookDispatchers.get(rdtHook)?.onCommitFiberRoot !== dispatchCommitFiberRoot) return;
1076
- _fiberRoots.add(root);
1077
- rootRendererIds.set(root, rendererID);
1097
+ const rootMemoizedState = root.current.memoizedState;
1098
+ const isUnmounting =
1099
+ rootMemoizedState === null ||
1100
+ rootMemoizedState.element === null ||
1101
+ rootMemoizedState.element === undefined;
1102
+ if (isUnmounting) {
1103
+ _fiberRoots.delete(root);
1104
+ rootRendererIds.delete(root);
1105
+ } else {
1106
+ _fiberRoots.add(root);
1107
+ rootRendererIds.set(root, rendererID);
1108
+ }
1078
1109
  for (const listener of commitFiberRootListeners) {
1079
- listener(rendererID, root, priority);
1110
+ listener(rendererID, root, priority, didError);
1080
1111
  }
1081
1112
  };
1082
1113
  dispatchers.onCommitFiberRoot = dispatchCommitFiberRoot;
@@ -1192,9 +1223,7 @@ export const instrument = (options: InstrumentationOptions): Unsubscribe => {
1192
1223
  });
1193
1224
  };
1194
1225
 
1195
- // React stamps fibers under per-renderer random-suffix keys (`__reactFiber$<suffix>`);
1196
- // caching discovered keys makes repeat lookups a single property read instead of a
1197
- // scan over every own key of the element.
1226
+ // React uses per-renderer suffixes for host-instance Fiber keys, so discovered keys are cached.
1198
1227
  const knownFiberPropertyKeys = new Set<string>();
1199
1228
 
1200
1229
  const isFiberPropertyKey = (key: string): boolean =>
@@ -1202,6 +1231,32 @@ const isFiberPropertyKey = (key: string): boolean =>
1202
1231
  key.startsWith("__reactInternalInstance$") ||
1203
1232
  key.startsWith("__reactFiber");
1204
1233
 
1234
+ const getLegacyRootFiber = (hostInstance: Record<string, unknown>): Fiber | null => {
1235
+ const reactRootContainer = hostInstance._reactRootContainer;
1236
+ if (!isObjectRecord(reactRootContainer)) return null;
1237
+ const internalRoot = reactRootContainer._internalRoot;
1238
+ if (!isObjectRecord(internalRoot)) return null;
1239
+ const current = internalRoot.current;
1240
+ if (!isObjectRecord(current)) return null;
1241
+ const child = current.child;
1242
+ return isValidFiber(child) ? child : null;
1243
+ };
1244
+
1245
+ const getInternalInstanceHandle = (hostInstance: Record<string, unknown>): Fiber | null => {
1246
+ const internalInstanceHandle =
1247
+ hostInstance.__internalInstanceHandle ?? hostInstance._internalInstanceHandle;
1248
+ return isValidFiber(internalInstanceHandle) ? internalInstanceHandle : null;
1249
+ };
1250
+
1251
+ const getPublicHostInstance = (stateNode: unknown): unknown => {
1252
+ if (!isObjectRecord(stateNode)) return stateNode;
1253
+ const canonical = stateNode.canonical;
1254
+ if (isObjectRecord(canonical) && isObjectRecord(canonical.publicInstance)) {
1255
+ return canonical.publicInstance;
1256
+ }
1257
+ return typeof stateNode._nativeTag === "number" ? stateNode._nativeTag : stateNode;
1258
+ };
1259
+
1205
1260
  export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
1206
1261
  const rdtHook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
1207
1262
  if (rdtHook?.renderers) {
@@ -1213,42 +1268,38 @@ export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
1213
1268
  }
1214
1269
  }
1215
1270
 
1216
- if (typeof hostInstance === "object" && hostInstance != null) {
1217
- if ("_reactRootContainer" in hostInstance) {
1218
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1219
- return (hostInstance._reactRootContainer as any)?._internalRoot?.current?.child;
1220
- }
1271
+ if (isObjectRecord(hostInstance)) {
1272
+ const legacyRootFiber = getLegacyRootFiber(hostInstance);
1273
+ if (legacyRootFiber) return legacyRootFiber;
1221
1274
 
1222
- // React Native Fabric public instances (ReactNativeElement /
1223
- // ReactFabricHostComponent) store their fiber as the "internal instance
1224
- // handle" instead of a __reactFiber$-prefixed key
1225
- const fabricInstanceHandle =
1226
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1227
- (hostInstance as any).__internalInstanceHandle ??
1228
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1229
- (hostInstance as any)._internalInstanceHandle;
1230
- if (isFiber(fabricInstanceHandle)) {
1231
- return fabricInstanceHandle;
1232
- }
1275
+ const internalInstanceHandle = getInternalInstanceHandle(hostInstance);
1276
+ if (internalInstanceHandle) return internalInstanceHandle;
1233
1277
 
1234
- const hostInstanceRecord = hostInstance as Record<string, unknown>;
1235
1278
  for (const knownKey of knownFiberPropertyKeys) {
1236
- const fiber = hostInstanceRecord[knownKey];
1237
- if (fiber) return fiber as Fiber;
1279
+ const fiber = hostInstance[knownKey];
1280
+ if (isValidFiber(fiber)) return fiber;
1238
1281
  }
1239
1282
 
1240
- for (const key of Object.keys(hostInstanceRecord)) {
1283
+ for (const key of Object.keys(hostInstance)) {
1241
1284
  if (isFiberPropertyKey(key)) {
1242
1285
  knownFiberPropertyKeys.add(key);
1243
- return (hostInstanceRecord[key] || null) as Fiber | null;
1286
+ const fiber = hostInstance[key];
1287
+ if (isValidFiber(fiber)) return fiber;
1244
1288
  }
1245
1289
  }
1290
+ }
1246
1291
 
1292
+ if (
1293
+ hostInstance !== null &&
1294
+ hostInstance !== undefined &&
1295
+ (typeof hostInstance === "object" || typeof hostInstance === "number")
1296
+ ) {
1247
1297
  for (const fiberRoot of _fiberRoots) {
1248
- if (getRootRenderer(fiberRoot.current)?.findFiberByHostInstance) continue;
1249
1298
  const fiber = traverseFiber(
1250
1299
  fiberRoot.current,
1251
- (candidateFiber) => candidateFiber.stateNode === hostInstance,
1300
+ (candidateFiber) =>
1301
+ isHostFiber(candidateFiber) &&
1302
+ getPublicHostInstance(candidateFiber.stateNode) === hostInstance,
1252
1303
  );
1253
1304
  if (fiber) return fiber;
1254
1305
  }