## Summary
V8 prefixes async call sites with `async ` when it prints a stack, like
this:
```
Error: boom
at inner (/tmp/asy.js:1:44)
at async outerName (/tmp/asy.js:2:30)
```
`parseStackTraceFromChromeStack` captures `async outerName` as the frame
name and strips the prefix here:
```js
} else if (name.startsWith('async ')) {
name = name.slice(5);
isAsync = true;
}
```
`'async '` is six characters, so `slice(5)` leaves the space behind and
the frame name comes back as `' outerName'` rather than `'outerName'`.
I noticed it while reading the parser, and it is not purely cosmetic:
that name is the first element of the `ReactFunctionLocation` returned
by `extractLocationFromComponentStack` and
`extractLocationFromOwnerStack`, which `backend/fiber/renderer.js`
stores as `instance.source`. Any async component whose frame reaches
that path is recorded under a name with a stray leading space.
The same line exists in
`packages/react-server/src/ReactFlightStackConfigV8.js`, which the
DevTools file is a copy of. After review I fixed it in this PR as well,
in a second commit. There it only matters on the fallback path that
parses an already formatted stack string, when the error's `stack` was
read or assigned before React reaches it. #37130 is open on that file
too, but it does not touch these lines.
## How did you test this change?
I first confirmed the format V8 actually emits, rather than assuming it:
```
$ node -e 'async function inner(){await null;throw new Error("boom")}
async function outerName(){await inner()}
outerName().catch(e=>console.log(e.stack))'
Error: boom
at inner ([eval]:1:52)
at async outerName ([eval]:2:33)
```
Then I added a case to the existing `extractLocationFromComponentStack`
block in `utils-test.js`. Against `main` it fails with exactly the
leading space:
```
● utils › extractLocationFromComponentStack › should strip the async prefix from a frame name
- Expected - 1
+ Received + 1
Array [
- "Comments",
+ " Comments",
"https://react.dev/_next/static/chunks/848-122f91e9565d9ffa.js",
5,
9236,
]
```
With the one-character fix applied:
```
$ yarn test --build --project=devtools -r=experimental utils-test
PASS packages/react-devtools-shared/src/__tests__/utils-test.js
Tests: 63 passed, 63 total
```
I also ran the whole DevTools project before and after to check I was
not moving anything else. Both runs end at `9 failed, 4 failed suites`,
the same test names each time (`componentStacks`, `console`,
`inspectedElement`, `legacy/inspectElement`), so those failures are
pre-existing on `main` in my environment and unrelated to this change.
The only difference between the two runs is my new test: 586 passed
before, 587 after.
For the Flight side I added `ReactFlightStackConfigV8-test.js`, which
assigns a formatted stack to an error and checks what `parseStackTrace`
returns. Against `main` it fails with the same leading space (`"
outerName"`); with the fix it passes on stable and experimental in
development. It is gated to `__DEV__` because that fallback goes through
the DEV-only stack cache. `ReactFlightServer-test` and
`ReactFlightAsyncDebugInfo-test` still pass next to it (23 tests).
`prettier` and `eslint` are clean on all changed files. `yarn flow
dom-node` reported no errors for the DevTools commit; I did not rerun
Flow after the one-character Flight change.
AI tools used
## Summary
Alternative to #37280 that keeps the child-set assertion and instead
fixes the root cause.
The assertion "The children should not have changed if we pass in the
same set." fired while DevTools reconciled the hidden content tree of a
Suspense boundary that had just switched to its fallback.
`updateSuspenseChildrenRecursively` reconciles the content and the
fallback in two passes, but the previous-set lockstep pointer of the
content pass is not bounded. When a boundary is suspended on both sides
of a commit, the pointer advances from the previous content Offscreen
onto the previous fallback fragment, and the leftover-children check
reports `ShouldResetChildren` even though the fallback is reconciled in
the second pass by design.
For a boundary that is filtered from the tree, that flag propagates to
the parent child list, freezes its lockstep pointer, forces the
following sibling to be paired by alternate, and the instance scan
(which only matches the paired previous fiber) no longer finds the
existing instance, since instances track the current fiber. The subtree
below is then walked without its instance, which cascades into spurious
unmount and remount work and surfaces at the assertion in the filtered
same-child-set branch.
We're also avoiding creation of new backend instances in those
scenarios.
This change bounds the previous set of the content pass by the previous
fallback fragment via a new `prevLastChild` parameter, so the flag
disappears.
Closes#37280
## How did you test this change?
- cherry-picked test from #37280
---------
Co-authored-by: Ruslan Lesiutin <hoxy@meta.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude Code (kimi-k3[1m]) <noreply@anthropic.com>
## Summary
There was a bug in the helper that unwraps component names like
Forget(Memo(Button)) into a base component name plus its HOC wrappers.
The regex was using the g flag, which means exec() remembers its
position via lastIndex. Since each iteration replaces the current string
with the shorter unwrapped inner string, lastIndex ends up pointing past
the end of the new string. The next exec() returns null, so the loop
stops after unwrapping only the outermost HOC.
component named Forget(Memo(ForgetMemoCounter))
before fixes ✨Memo(ForgetMemoCounter)
after fixes ✨🧠ForgetMemoCounter
component named Forget(ForwardRef(ForgetForwardRefCounter))
before fixes ✨ForwardRef(ForgetForwardRefCounter)
after fixes ✨ForgetForwardRefCounter
## How did you test this change?
Tested the change locally in `devtool` and added tests for the same
**Before**
<img width="1920" height="690" alt="devtools-hoc-BEFORE-buggy"
src="https://github.com/user-attachments/assets/14e7e632-da97-43fb-867b-9da3b9d7cb22"
/>
**After**
<img width="1920" height="690" alt="devtools-hoc-AFTER-fixed"
src="https://github.com/user-attachments/assets/4fe91b78-af11-4584-986b-b7aa9a03d0b6"
/>
Not sure if we need a new fixture can add one if required
## Summary
Added the search by component name functionality as requested for
https://github.com/react/react/issues/32995#issuecomment-4786856255
Adds a component search to the Profiler's commit view, so you can find a
specific component within the currently selected commit (Flamegraph &
Ranked charts). Previously the only search lived in the Components panel
and covered the live tree, not profiling data.
Behavior is inspired from Chrome DevTools' in-page find:
- Cmd/Ctrl+F opens a collapsible search box floating over the chart (no
always-on input).
- Shows an N | M match count; ↑/↓ buttons and Enter / Shift+Enter step
through matches (with wraparound).
- Each match is selected via the existing selectFiber, so it highlights,
zooms, updates the sidebar, syncs to the Components tab, and scrolls
into view.
- Esc or ✕ closes it.
- Search is scoped to the selected commit only — never the whole trace.
Switching commits re-scopes the count.
## How did you test this change?
https://github.com/user-attachments/assets/ab2396e1-f329-4213-b053-9b3d08988c6b
## Summary
With the tab gone, everything that fed it is unreachable. This deletes
`packages/react-devtools-timeline` (74 files) and the backend that
produced its data, `backend/profilingHooks.js`, along with
`SidebarEventInfo`, the two timeline test suites, the `timelineData`
snapshot serializer, and the scheduling-profiler fixture.
It also unwires the plumbing that only existed to carry timeline data:
`recordTimeline` across the reload-and-profile path (hook →
sessionStorage → agent → renderer), `timelineData` on
`ProfilingDataBackend` and the profile export, the `supportsTimeline`
Store config, the `rootSupportsTimelineProfiling` capability, the
`DevToolsProfilingHooks` type and the `ReactRenderer` members DevTools
used to inject it, the 40 `--color-timeline-*` theme variables in both
themes plus the orphaned `--color-scroll-caret`, and `hook.js`'s
internal-module-range tracking with its `react-devtools-facade` stubs.
`yarn.lock` is regenerated: 52 distinct package-versions and 68
requirement specs drop out, with no additions and no version changes to
anything that remains.
## Deliberate non-changes
- **`PROFILER_EXPORT_VERSION` stays at 5.**
`prepareProfilingDataFrontendFromExport` compares versions with `!==`,
so a bump would reject every profile anyone has already saved.
`timelineData` was an optional key, so dropping it is invisible in both
directions.
- **Profiling flag bit `0b010` is retired, not reused**, and the
constant is replaced by a comment saying so. Shipped backends keep
setting it, so renumbering `PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT`
into that slot would make a new frontend misread older backends as
tracks-capable.
- **The `displayName` properties on DevTools' cache thenables are
kept.** They look timeline-only, but `ReactFiberThenable` reads
`thenable.displayName` to name I/O in async debug info, which feeds the
Performance tracks. Only their stale comments are corrected.
- **`react-reconciler`, `shared/ReactFeatureFlags.js` and
`scripts/rollup` are untouched**; `enableSchedulingProfiler` is still
live for www and native-fb.
## Follow-ups (not in this stack)
Three stale comments still name the removed package:
`scripts/rollup/wrappers.js:532` and `ReactFiberLane.js:38,125`. Left
alone to keep this stack purely DevTools-side.
## Test plan
`yarn linc`, `yarn flow dom-node`, and the DevTools suite all pass on
this commit in isolation (40/40 suites, 582 tests).
Cleaning up the Timeline profiler in the next commit on top of this one.
If user is debugging React 19.2+, we will show a suggestion to record a
trace on Performance panel. Otherwise, we will suggest to upgrade to
React 19.2 to unlock Performance tracks.
<img width="751" height="832" alt="Screenshot 2026-08-03 at 14 57 43"
src="https://github.com/user-attachments/assets/153e712b-8f7c-4ec5-87f8-b01cf1180aae"
/>
Previously, every instance of ErrorBoundary, which wraps every custom
panel in extension, was subscribing to errors from the Store. This would
report the same error for every mounted panel.
ErrorBoundary now only intercepts render-time errors, and Store errors
are captured and reported in an external subscription at the place where
Store is created.
Buffers Bridge messages during extension port reconnects and adds a
readiness handshake for ordered queue flushing. Includes regression
coverage for reconnect delivery and listener cleanup.
Potential scenario could be a long user session, where Chrome kills one
of the extension ports to save resources and then user re-connects by
navigating back to the DevTools UI.
Builds on #37049 by validating Store operation invariants before
mutation. Missing nodes, invalid element types, inconsistent
parent-child relationships, and invalid reorder operations now emit and
throw explicit errors instead of silently continuing with corrupted
state.
Adds a canonical-render regression test for invalid child removal.
Builds on #37048 by replacing `any`-based Bridge and Wall boundaries
with typed `mixed` values and explicit runtime validation. Invalid
messages and post-shutdown operations now throw, while shutdown reliably
flushes queued messages even if cleanup fails.
Strengthens the DevTools Bridge and Wall contracts:
- Models event dictionaries as event-to-payload maps, using `void` for
events without payloads.
- Types `send(event, payload?)` directly, eliminating runtime
payload-arity handling.
- Replaces broad `any` transport types with `mixed` and boundary
validation.
- Throws on invalid lifecycle usage instead of warning or silently
returning.
- Ensures shutdown flushes queued messages even when Wall cleanup fails.
- Updates Wall implementations and adds Bridge lifecycle coverage.
Ensures the standalone DevTools Bridge fully shuts down when its
WebSocket closes, with re-entrancy protection. Adds tests confirming
event-only shutdown leaves the Bridge active while socket closure shuts
it down.
Tightens `EventEmitter` listener types and fixes error handling so the
first thrown value is preserved while subsequent errors are reported
instead of swallowed. Adds regression coverage for listener failures.
## Summary
In a large react app, especially when components having similar starting
names like Table, TableColumn, TableCell, TableRow all together 100+
components when rendered in a virtualized table. Traversing the search
result is sometimes difficult with scroll
The component tree search only let you step through matches one at a
time (Enter / Shift+Enter). In large apps with many similarly-named
components (Table, TableRow, TableCell, ...) a search can return 100+
matches in a virtualized list, making a specific match tedious to reach.
- the result counter is an editable, live-scrubbing
index field: typing a number scrolls to that match as you type
(clamped to range)
- Fixes re-search getting stuck, clearing the box and retyping the same
term while a match was still selected snapped back to that same
component. It now advances to the next match (find-next semantics).
## How did you test this change?
Adds a SearchableTable example to the DevTools shell and unit tests for
the new action and the retype behavior.
https://github.com/user-attachments/assets/7ea9801a-7bcb-4e8f-bf73-a5307a0fdbae
cc @hoxyq Let me know what do you feel about this feature, if its
helpful for devtools.
`printOperationsArray` in `react-devtools-shared` walks an operations
array under
the invariant that each `switch` case leaves `i` pointing at the next
opcode (loop
header at `packages/react-devtools-shared/src/utils.js`).
The `TREE_OPERATION_APPLIED_ACTIVITY_SLICE_CHANGE` case broke that
invariant:
```js
case TREE_OPERATION_APPLIED_ACTIVITY_SLICE_CHANGE: {
i++; // skip opcode -> i now at the value slot
const activitySliceIDChange = operations[i + 1]; // reads the slot AFTER the value; i not advanced
...
}
```
The operation is exactly two slots, `[opcode, activitySliceID]` (see the
writer in
`packages/react-devtools-shared/src/backend/fiber/renderer.js`, which
pushes the
opcode then the id). So the case did two things wrong:
1. It logged the wrong number: `operations[i + 1]` reads the slot
*after* the
value (the next operation's opcode, or `undefined` at the end of the
array).
2. It left `i` pointing at the value slot, so the outer `while (i <
operations.length)` loop re-read the activity-slice id as an opcode. For
any
non-zero slice id that falls through to `default: throw
Error("Unsupported
Bridge operation ...")`, aborting the whole dump.
The two canonical decoders of this same operation both use the correct
pattern
(skip the opcode, then read *and* advance past the value):
- `devtools/store.js`: `i++; nextActivitySliceID = operations[i++];`
- `devtools/views/Profiler/CommitTreeBuilder.js`: `i++; const
activitySliceIDChange = operations[i++];`
This change makes `printOperationsArray` match them by reading
`operations[i++]`.
This is a debug-only diagnostic path: the only caller is the
`__DEBUG__`-guarded
dump in `backend/legacy/renderer.js`, so it is not a production crash.
The bug was
introduced in #34908.
## How did you test this change?
Added a regression test for `printOperationsArray` in
`packages/react-devtools-shared/src/__tests__/utils-test.js`. The
fixture chains
two activity-slice operations, `[rendererID, rootID, stringTableSize=0,
opcode, 42,
opcode, 0]`; the trailing operation is what forces the reader to advance
past the
first value slot rather than re-read it. It asserts the call does not
throw, logs
once, and that the message contains both `Applied activity slice change
to 42` and
`Reset applied activity slice`.
Ran the DevTools Jest project (built first, as that project requires a
build):
- With the fix: 51/51 pass, including the new test.
- Reverting only the one-line fix back to `operations[i + 1]` and
rebuilding: the
new test fails with `Unsupported Bridge operation "42"` (exactly the
predicted
failure), 50 pass / 1 fail. Restored the fix and it is green again.
`yarn prettier-check` and `yarn linc` are clean on the changed files.
`formatConsoleArguments` in
`packages/react-devtools-shared/src/backend/utils/formatConsoleArguments.js`
is used by the DevTools backend (via `hook.js`) to inline `console.*`
printf-style substitutions after stripping React's appended component
stack.
For `%s`/`%d`/`%i`/`%f` it consumes the next argument with
`args.splice(argumentsPointer, 1)` and formats the result.
When a format string has more specifiers than arguments, `splice`
returns an
empty array, so `arg` is `undefined` and the specifier is rendered as
text:
`%s` becomes `"undefined"`, and `%d`/`%i`/`%f` become `"NaN"`.
```js
formatConsoleArguments('%s %s', 'the');
// before: ['the undefined']
// after: ['the %s']
```
Browsers and Node's `util.format` leave an unmatched specifier as a
literal
(`console.log('%s %s', 'a')` prints `a %s`; `console.log('%d')` prints
`%d`).
So a message like `console.warn('value: %s')` was shown in DevTools as
`value: undefined` instead of `value: %s`.
This guards each of the `%d`/`%i`, `%f`, and `%s` cases on argument
availability (`argumentsPointer >= args.length`): when nothing is left
to
consume it keeps the specifier text and does not splice, mirroring the
existing trailing-`%` handling added in #36852. An explicitly passed
`undefined`/`null` argument is unchanged and still renders as
`undefined`/`null`, since a value is present at that position (the
`formats nullish values` test still passes).
## How did you test this change?
Added a regression test to the existing `formatConsoleArguments`
describe
block in `packages/react-devtools-shared/src/__tests__/utils-test.js`:
```js
it('keeps specifiers literal when no argument is supplied', () => {
expect(formatConsoleArguments('%s %s', 'the')).toEqual(['the %s']);
expect(formatConsoleArguments('%s %d', 'value')).toEqual(['value %d']);
expect(formatConsoleArguments('%s %i', 'value')).toEqual(['value %i']);
expect(formatConsoleArguments('%s %f', 'value')).toEqual(['value %f']);
});
```
Each assertion fails on `main` (it produces `['the undefined']` and
`['value NaN']`) and passes with the fix.
Commands run locally:
```
yarn test --build --project devtools packages/react-devtools-shared/src/__tests__/utils-test.js
# 51 passed, 51 total
yarn lint packages/react-devtools-shared/src/backend/utils/formatConsoleArguments.js \
packages/react-devtools-shared/src/__tests__/utils-test.js
# Lint passed.
yarn flow dom-node
# No errors!
yarn prettier-check packages/react-devtools-shared/src/backend/utils/formatConsoleArguments.js \
packages/react-devtools-shared/src/__tests__/utils-test.js
# clean
```
Cross-checked the expected output against Node `util.format`:
`util.format('%s %s', 'the')` -> `the %s`; `util.format('%d')` -> `%d`.
`formatConsoleArgumentsToSingleString` in
`packages/react-devtools-shared/src/backend/utils/index.js`
inlines `console.*` printf-style substitutions into a single string.
That string is
used both as the dedup key and as the displayed text for per-component
warnings/errors.
The `switch` that consumes the captured flag handles `s`, `d`, `i`, and
`f`, and the
function's own header comment says it "Implements s, d, i and f
placeholders". But
the substitution regex only captured `[jds]`:
```js
const REGEXP = /(%?)(%([jds]))/g;
```
So `%i` and `%f` were never matched. The `case 'i'` and `case 'f'` arms
were dead
code: the specifier was emitted literally and its argument was never
consumed. Worse,
because the unmatched specifier does not shift its argument, every
following specifier
in the same format string then binds to the wrong argument (a cascading
off-by-one
over the remaining args).
`%i` and `%f` are standard console integer/float specifiers (Node
`util.format` and
browsers both support them), so this affected common log formats. The
fix adds `i`
and `f` to the regex class so the existing switch arms run:
```js
const REGEXP = /(%?)(%([jdisf]))/g;
```
This is a one-character-class change that reconciles the regex with the
switch and
the header comment. The pre-existing behavior that `%j` is matched but
has no
`case` (so it falls through unchanged) is intentionally left as-is; it
is out of
scope for this fix.
## How did you test this change?
Added three regression tests to the existing
`formatConsoleArgumentsToSingleString`
describe block in
`packages/react-devtools-shared/src/__tests__/utils-test.js`:
- `formatConsoleArgumentsToSingleString('%i', 3.14)` -> `'3'`
- `formatConsoleArgumentsToSingleString('%f', 3.5)` -> `'3.5'`
- `formatConsoleArgumentsToSingleString('a %i b %s', 7, 'x')` -> `'a 7 b
x'` (locks
in argument alignment)
Commands run locally (experimental devtools bundles):
```
yarn build-for-devtools
yarn test --build --project=devtools -r=experimental packages/react-devtools-shared/src/__tests__/utils-test.js
```
Result: `Tests: 53 passed, 53 total`.
To confirm the tests actually cover the bug, I reverted the
one-character fix back to
`[jds]` and reran: the three new tests fail exactly as the bug predicts,
e.g. `%i`
yields `"%i 3.14"` and `a %i b %s` yields `"a %i b 7 x"` (the `%s` binds
to `7`
instead of `x`, showing the off-by-one). Restoring the fix makes them
pass again.
Also green:
```
yarn linc # ESLint on changed files: passed
yarn prettier # no files reflagged
yarn flow dom-node # No errors!
```
## Summary
`formatConsoleArguments` (used by the DevTools backend to inline console
substitutions) walks the format string and inlines `%s`/`%d`/`%i`/`%f`
arguments while leaving `%c`/`%o`/`%O` in place. For each `%` it reads
the **next** character to decide what to do.
When the format string ends with a lone `%` — e.g.
`console.log('Progress 100%', value)` — the character after `%` is
`undefined`, and the `default` branch ran:
```js
template += `%${nextChar}`; // -> "%undefined"
```
So the function emitted the literal text `%undefined`:
```js
formatConsoleArguments('Progress 100%', 'extra');
// before: ['Progress 100%undefined', 'extra']
// after: ['Progress 100%', 'extra']
```
Browsers render a trailing `%` in a console format string as a literal
percent sign, so this PR keeps it as `%` when there is no following
character.
## How did you test this change?
Added a `keeps a trailing percent sign` test to the existing
`formatConsoleArguments` suite in `utils-test.js` (covering both a bare
trailing `%` and one that follows another substitution). Since the
function is pure and import-free, I also verified the patched logic
against every existing case in that suite plus the new ones to confirm
there are no regressions.
## Summary
This diff bumps a few hermes-parser related dependencies so that it can
consume modern Flow syntax. In addition, it makes targeted changes to
two files that will be synced to react-native so that we can enforce we
only use modern flow syntax in react-native.
## How did you test this change?
flow
## Summary
Mostly changing the casting syntax from `(x: Y)` to `x as Y`, as the old
syntax was deprecated and always causing an error in Flow
## How did you test this change?
`yarn flow-ci`
## Summary
Notable changes:
- Only $FlowFixMe, $FlowExpectedError suppression comments are supported
- All suppression comments need error code
- A lot of new invalid-compare and constant-condition errors suppressed.
These errors reveal potential logical bugs
## How did you test this change?
`yarn flow-ci`
For JavaScript runtimes that do not have
[`Reflect`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect)
supported, we had a fall back that was calling the constructor with
overridden `this` context via
```
fn.apply(Fake.prototype);
```
In ES6, it is required to call constructor only with the `new` keyword,
otherwise the runtime is expected to throw a corresponding TypeError:
```
TypeError: Class constructor <> cannot be invoked without 'new'
```
We've observed this error in Hermes runtime, but this is applicable to
V8 or any other runtime. The only reason why V8 wasn't affected is
because it implemented Reflect APIs.
Instead of the incorrect call, we will fall back to calling `new fn()`,
but with a temporary patched prototype of the class, which would make a
trap out of the setter for `props` object. We use the same approach when
`Reflect` APIs are available, but instead of modifying the prototype, we
pass the fake context:
https://github.com/facebook/react/blob/d5736f098edee62c44f27b053e6e48f5fa443803/packages/shared/ReactComponentStackFrame.js#L129-L148
---
See tests implemented. Without the changes, the test would fail with the
`TypeError` mentioned above.
## Summary
`getDataType` collapsed both `Infinity` and `-Infinity` to the
`'infinity'` data type, so a `-Infinity` value coming from inspected
props/state/hooks was rehydrated on the frontend as `Infinity`.
This adds a `'-infinity'` `DataType`, routes it through
`dehydrate`/`hydrate` alongside the existing `'infinity'` arm, and makes
`smartParse`/`smartStringify` (used for editable hook values) symmetric.
## Files
- `packages/react-devtools-shared/src/utils.js` — extend `DataType`,
split sign in `getDataType`, route `'-infinity'` through
`formatDataForPreview`.
- `packages/react-devtools-shared/src/hydration.js` — `dehydrate` and
`hydrate` cases for `'-infinity'`.
- `packages/react-devtools-shared/src/devtools/utils.js` — `smartParse`
accepts `'-Infinity'`; `smartStringify` returns `'-Infinity'` for
negative infinite values.
-
`packages/react-devtools-shared/src/__tests__/inspectedElement-test.js`
and `legacy/inspectElement-test.js` — added `minus_infinity={-Infinity}`
to the simple-data-types tests + snapshots.
-
`packages/react-devtools-shell/src/app/InspectableElements/SimpleValues.js`
— added `minusInfinity` to the dev shell so the path is exercised
manually.
## Test plan
- [x] `yarn prettier` / `yarn linc`
- [x] `yarn flow dom-node` — no errors
- [x] `yarn test --silent --no-watchman -t "should support simple data
types"` (source channel)
- [x] `yarn test-www --silent --no-watchman -t "should support simple
data types"` (www-modern)
- [ ] `yarn test-build-devtools` — relies on a built bundle; left to CI
per repo policy.
Fixes#32552
Fixes#17855
When hovering a component in the DevTools Components inspector, a
highlight overlay appears on the inspected page. The highlight is
cleared via `onMouseLeave` on the tree container `div`. But this React
synthetic event only fires when the pointer transitions between elements
_within the same document_. When the user moves their mouse out of the
DevTools panel window entirely (e.g. to the browser viewport), no
element in the React tree receives `mouseleave`, so
`clearHostInstanceHighlight` is never sent over the bridge and the
overlay persists on the page.
The fix adds a native `mouseleave` listener on the DevTools panel's
`ownerDocument` in `Tree.js`. When the pointer exits the panel viewport,
it fires `clearHighlightHostInstance` and removes the overlay. Using
`ownerDocument` (rather than document) is consistent with the existing
pattern in `Tree.js` for browser extension compatibility.
How did you test this change?
Tested manually using the Chrome extension:
1. Opened React DevTools → Components tab on a React app
2. Hovered a component in the tree — highlight appeared on the page ✓
3. Moved the mouse out of the DevTools panel into the browser viewport —
highlight cleared immediately ✓ (previously it persisted)
4. Moved the mouse back into the panel and hovered a component —
highlighting still works normally ✓
5. Unhovered within the panel — highlight still clears correctly ✓
Ran the DevTools test suite: yarn test --no-watchman ReactDevTools — all
tests pass.
Fixed spelling errors in comments and error messages:
- Fixed 'occured' -> 'occurred' in ReactAsyncActions-test.js
- Fixed 'teh' -> 'the' in ReactFiberConfigDOM.js
- Fixed 'occured' -> 'occurred' in ErrorBoundary.js
- Fixed 'accomodate' -> 'accommodate' in InferMutationAliasingEffects.ts
<!--
Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.
Before submitting a pull request, please make sure the following is
done:
1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
10. If you haven't already, complete the CLA.
Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->
## Summary
<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->
## How did you test this change?
<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
If you leave this empty, your PR will very likely be closed.
-->
I am in a process of splitting down the renderer implementation into
smaller units of logic that can be reused. This change is about
extracting pure functions only.
After https://github.com/facebook/react/pull/34089, when updating
(possibly, mounting) inside disconnected subtree, we don't record this
as an operation. This only happens during reconnect. The issue is that
`recordProfilingDurations()` can be called, which diffs tree base
duration and reports it to the Frontend:
https://github.com/facebook/react/blob/65db1000b944c8a07b5947c06b38eb8364dce4f2/packages/react-devtools-shared/src/backend/fiber/renderer.js#L4506-L4521
This operation can be recorded before the "Add" operation, and it will
not be resolved properly on the Frontend side.
Before the fix:
```
commit tree › Suspense › should handle transitioning from fallback back to content during profiling
Could not clone the node: commit tree does not contain fiber "5". This is a bug in React DevTools.
162 | const existingNode = nodes.get(id);
163 | if (existingNode == null) {
> 164 | throw new Error(
| ^
165 | `Could not clone the node: commit tree does not contain fiber "${id}". This is a bug in React DevTools.`,
166 | );
167 | }
at getClonedNode (packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js:164:13)
at updateTree (packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js:348:24)
at getCommitTree (packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js:112:20)
at ProfilingCache.getCommitTree (packages/react-devtools-shared/src/devtools/ProfilingCache.js:40:46)
at Object.<anonymous> (packages/react-devtools-shared/src/__tests__/profilingCommitTreeBuilder-test.js:257:44)
```