Stacked on #34892.
In the timeline scrubber each timeline entry gets a label and color
assigned based on the environment computed for that step.
In the rects, we find the timeline step that this boundary is part of
and use that environment to assign a color. This is slightly different
than picking from the boundary itself since it takes into account parent
boundaries.
In the "suspended by" section we color each entry individually based on
the environment that spawned the I/O.
<img width="790" height="813" alt="Screenshot 2025-10-17 at 12 18 56 AM"
src="https://github.com/user-attachments/assets/c902b1fb-0992-4e24-8e94-a97ca8507551"
/>
Stacked on #34885.
This refactors the timeline to store not just an id but a complex object
for each step. This will later represent a group of boundaries.
Each timeline step is assigned an environment name. We pick the last
environment name (assumed to have resolved last) from the union of the
parent and child environment names. I.e. a child step is considered to
be blocked by the parent so if a child isn't blocked on any environment
name it still gets marked as the parent's environment name.
In a follow up, I'd like to reorder the document order timeline based on
environment names to favor loading everything in one environment before
the next.
Stacked on #34881.
We don't paint suspense boundaries if there are no suspenders. This does
the same with the root. The root is still selectable so you can confirm
but there's no affordance drawing attention to click the root.
This could happen if you don't use the built-ins of React to load things
like scripts and css. It would never happen in something like Next.js
where code and CSS is loaded through React-native like RSC.
However, it could also happen in the Activity scoped case when all
resources are always loaded early.
Stacked on #34880.
In #34861 I removed the highlight of the real view when hovering the
timeline since it was disruptive to stepping through the visuals.
This makes it so that when we hover the timeline we highlight the rect
with the subtle hover effect added in #34880.
We can now just use the one shared state for this and don't need the CSS
psuedo-selectors.
<img width="603" height="813" alt="Screenshot 2025-10-16 at 3 11 17 PM"
src="https://github.com/user-attachments/assets/a018b5ce-dd4d-4e77-ad47-b4ea068f1976"
/>
<img width="1011" height="811" alt="Screenshot 2025-10-16 at 2 20 46 PM"
src="https://github.com/user-attachments/assets/6dea3962-d369-4823-b44f-2c62b566c8f1"
/>
The selection is now clearer with a wider outline which spans the
bounding box if there are multi rects.
The color now gets darked changes on hover with a slight animation.
The colors are now mixed from constants defined which are consistently
used in the rects, the time span in the "suspended by" side bar and the
scrubber. I also have constants defined for "server" and "other" debug
environments which will be used in a follow up.
Using `renderToReadableStream` in Node.js with binary data from
`fs.readFileSync` (or `Buffer.allocUnsafe`) could cause downstream
consumers (like compression middleware) to fail with "Cannot perform
Construct on a detached ArrayBuffer".
The issue occurs because Node.js uses an 8192-byte Buffer pool for small
allocations (< 4KB). When React's `VIEW_SIZE` was 2KB, files between
~2KB and 4KB would be passed through as views of pooled buffers rather
than copied into `currentView`. ByteStreams (`type: 'bytes'`) detach
ArrayBuffers during transfer, which corrupts the shared Buffer pool and
causes subsequent Buffer operations to fail.
Increasing `VIEW_SIZE` from 2KB to 4KB ensures all chunks smaller than
4KB are copied into `currentView` (which uses a dedicated 4KB buffer
outside the pool), while chunks 4KB or larger don't use the pool anyway.
Thus no pooled buffers are ever exposed to ByteStream detachment.
This adds 2KB memory per active stream, copies chunks in the 2-4KB range
instead of passing them as views (small CPU cost), and buffers up to 2KB
more data before flushing. However, it avoids duplicating large binary
data (which copying everything would require, like the Edge entry point
currently does in `typedArrayToBinaryChunk`).
Related issues:
- https://github.com/vercel/next.js/issues/84753
- https://github.com/vercel/next.js/issues/84858
As part of the new inference model we updated to (correctly) treat
destructuring spread as creating a new mutable object. This had the
unfortunate side-effect of reducing precision on destructuring of props,
though:
```js
function Component({x, ...rest}) {
const z = rest.z;
identity(z);
return <Stringify x={x} z={z} />;
}
```
Memoized as the following, where we don't realize that `z` is actually
frozen:
```js
function Component(t0) {
const $ = _c(6);
let x;
let z;
if ($[0] !== t0) {
const { x: t1, ...rest } = t0;
x = t1;
z = rest.z;
identity(z);
...
```
#34341 was our first thought of how to do this (thanks @poteto for
exploring this idea!). But during review it became clear that it was a
bit more complicated than I had thought. So this PR explores a more
conservative alternative. The idea is:
* Track known sources of frozen values: component props, hook params,
and hook return values.
* Find all object spreads where the rvalue is a known frozen value.
* Look at how such objects are used, and if they are only used to access
properties (PropertyLoad/Destructure), pass to hooks, or pass to jsx
then we can be very confident the object is not mutated. We consider any
such objects to be frozen, even though technically spread creates a new
object.
See new fixtures for more examples.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34900).
* __->__ #34900
* #34887
In my previous PR I fixed some cases but broke others. So, new approach.
Two phase algorithm:
* First pass is forward data flow to determine all usages of macros.
This is necessary because many of Meta's macros have variants that can
be accessed via properties, eg you can do `macro(...)` but also
`macro.variant(...)`.
* Second pass is backwards data flow to find macro invocations (JSX and
calls) and then merge their operands into the same scope as the macro
call.
Note that this required updating PromoteUsedTemporaries to avoid
promoting macro calls that have interposing instructions between their
creation and usage. Macro calls in general are pure so it should be safe
to reorder them.
In addition, we're now more precise about `<fb:plural>`, `<fbt:param>`,
`fbt.plural()` and `fbt.param()`, which don't actually require all their
arguments to be inlined. The whole point is that the plural/param value
is an arbitrary value (along with a string name). So we no longer
transitively inline the arguments, we just make sure that they don't get
inadvertently promoted to named variables.
One caveat: we actually don't do anything to treat macro functions as
non-mutating, so `fbt.plural()` and friends (function form) may still
sometimes group arguments just due to mutability inference. In a
follow-up, i'll work to infer the types of nested macro functions as
non-mutating.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34887).
* #34900
* __->__ #34887
This is a great validation, so let's enable by default. Changes:
* Move the validation logic into ValidateUseMemo alongside the new check
that the useMemo result is used
* Update the lint description
* Make the void memo errors lint-only, they don't require us to skip
compilation (as evidenced by the fact that we've had this validation
off)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34882).
* #34855
* __->__ #34882
Two additional validations for useMemo:
* Disallow reassigning to values declared outside the useMemo callback
(always on)
* Disallow unused useMemo calls (part of the validateNoVoidUseMemo
feature flag, which in turn is off by default)
We should probably enable this flag though!
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34868).
* #34855
* #34882
* __->__ #34868
Added the standard Meta Platforms, Inc. MIT license notice to the top of
the feature flag comparison script to ensure compliance with repository
licensing requirements and for code consistency.
**No functional or logic changes were made to the code.**
## Summary
Fixes https://github.com/facebook/react/issues/34793.
We are allowing passing down effect events when they are inlined as a
prop.
```
<Child onClick={useEffectEvent(...)} />
```
This seems like a case that someone not familiar with `useEffectEvent`'s
purpose could fall for so this PR introduces logic to disallow its
usage.
An alternative implementation would be to modify the name and function
of `recordAllUseEffectEventFunctions` to record all `useEffectEvent`
instances either assigned to a variable or not, but this seems clearer.
Or we could also specifically disallow its usage inside JSX. Feel free
to suggest any improvements.
## How did you test this change?
- Added a new test in
`packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js`.
All tests pass.
## Summary
When upgrading to `babel-plugin-react-compiler@1.0.0` in a project that
uses `zod@3` we are running into TypeScript errors like:
```
node_modules/babel-plugin-react-compiler/dist/index.d.ts:435:10 - error TS2694: Namespace '"/REDACTED/node_modules/zod/v3/external"' has no exported member 'core'.
435 }, z.core.$strip>>>;
~~~~
```
This problem seems to be related to
d6eb735938, which introduced zod v3/v4
compatibility. Since `zod` is bundled into the compiler source this does
not cause runtime issues and only manifests as TypeScript errors. My
proposed solution is this PR is to use zod's [subpath versioning
strategy](https://zod.dev/v4/versioning?id=versioning-in-zod-4) which
allows you to support v3 and v4 APIs on both major versions.
Changes in this PR include:
- Updated `zod` import paths to `zod/v4`
- Bumped min `zod` version to `^3.25.0` for zod which guarantees the
`zod/v4` subpath is available.
- Updated `zod-validation-error` import paths to
`zod-validation-error/v4`
- Bumped min `zod-validation-error ` version to `^3.5.0`
- Updated `externals` tsup configuration where appropriate.
Once the compiler drops zod v3 support we could optionally remove the
`/v4` subpath from the imports.
## How did you test this change?
Not totally sure the best way to test. I ran `NODE_ENV=production yarn
workspace babel-plugin-react-compiler run build --dts` and diffed the
`dist/` folder between my change and `v1.0.0` and it looks correct. We
have a `patch-package` patch to workaround this for now and it works as
expected.
```diff
diff --git a/node_modules/babel-plugin-react-compiler/dist/index.d.ts b/node_modules/babel-plugin-react-compiler/dist/index.d.ts
index 81c3f3d..daafc2c 100644
--- a/node_modules/babel-plugin-react-compiler/dist/index.d.ts
+++ b/node_modules/babel-plugin-react-compiler/dist/index.d.ts
@@ -1,7 +1,7 @@
import * as BabelCore from '@babel/core';
import { NodePath as NodePath$1 } from '@babel/core';
import * as t from '@babel/types';
-import { z } from 'zod';
+import { z } from 'zod/v4';
import { NodePath, Scope } from '@babel/traverse';
interface Result<T, E> {
```
Co-authored-by: Henry Q. Dineen <henryqdineen@gmail.com>
This ensures that the outline of a previous rectangle lines up on the
same pixel as the next rectangle so that they appear consecutive.
<img width="244" height="51" alt="Screenshot 2025-10-16 at 11 35 32 AM"
src="https://github.com/user-attachments/assets/75ffde6f-8cc6-49c1-8855-3953569546b4"
/>
I don't love this implementation. There's probably a smarter way. Was
trying to avoid adding another element.
Currently the sub-pixel precision is lost which can lead to things not
lining up properly and being slightly off or overlapping.
We need some sub-pixel precision.
Ideally we'd just keep the floating point as is. I'm not sure why the
operations is limited to integers. We don't send it as a typed array
anyway it seems which would ideally be more optimal. Even if we did, we
haven't defined a precision for the protocol. Is it 32bit integer?
64bit? If it's 64bit we can fit a float anyway. Ideally it would be more
variable precision like just pushing into a typed array directly with
the option to write whatever precision we want.
Add inspection button to Suspense tab which lets you select only among
Suspense nodes. It highlights all the DOM nodes in the root of the
Suspense node instead of just the DOM element you hover. The name is
inferred.
<img width="1172" height="841" alt="Screenshot 2025-10-15 at 8 03 34 PM"
src="https://github.com/user-attachments/assets/f04d965b-ef6e-4196-9ba0-51626148fa1a"
/>
We now do a single pass over the HIR, building up two data structures:
* One tracks values that are known macro tags or macro calls.
* One tracks operands of macro-related instructions so that we can later
group them.
After building up these data structures, we do a pass over the latter
structure. For each macro call instruction, we recursively traverse its
operands to ensure they're in the same scope. Thus, something like
`fbt('hello' + fbt.param(foo(), "..."))` will correctly merge the fbt
call, the `+` binary expression, the `fbt.param()` call, and `foo()`
into a single scope.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34865).
* #34855
* __->__ #34865
We should only persist a selection once you click. Currently, we persist
the selection if you just hover which means you lose your selection
immediately when just starting to inspect. That's not what Chrome
Elements tab does - it selects on click.
I find it very frustrating that the highlight covers up the content that
I'm trying to review when stepping through the timeline. It also
triggered on keyboard navigation due to the focus which was annoying.
We could highlight something in the rects instead potentially.
In InferTypes when we infer types for properties during destructuring,
we were breaking out of the loop when we encounter a hole in the array.
Instead we should just skip that element and continue inferring later
properties.
Closes#34748
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34847).
* #34855
* __->__ #34847
This revealed that a lot of the event types were defined on the wrong
end of the bridge.
It was also a problem that events with the same name couldn't have
different arguments.
I get the wish to click the shadow but not all child boundaries are
within the bounds of the outer Suspense boundary's node.
Sometimes they overflow naturally and if we make it overflow hidden we
hide the boundaries. Maybe it would be ok if they're actually clipped by
the real DOM but right now it covers up boundaries that should be there.
Additionally, there's also a common case where the parent boundary
shrinks when suspending the children. That then causes the suspended
child boundaries to be clipped so that you can't restore them. Maybe the
virtual boundary shouldn't shrink in this case.
We can't measure Text nodes directly but we can measure a Range around
them.
This is useful since it's common, at least in examples, to use text
nodes as children of a Suspense boundary. Especially fallbacks.
We already do this in the update pass. That's what
`shouldMeasureSuspenseNode` does.
We also don't update measurements when we're inside an offscreen tree.
However, we didn't check if the boundary itself was in a suspended state
when in the `measureUnchangedSuspenseNodesRecursively` path.
This caused boundaries to disappear when their fallback didn't have a
rect (including their timeline entries).
Treat fake eval anonymous stacks as built-in. Hide built-in stack frames
unless they're used to call into a non-ignored stack frame.
The two main things to fix here is that 1) we're showing a linkified
stack for fake anonymous and 2) we're showing only built-ins when the
stack is completely internal. Meaning framework code is all noise.
Fixes https://github.com/facebook/react/issues/34770.
We need to clear measures at some point, otherwise all these copies of
props that we end up recording will allocate too much memory in
Chromium. This adds `performance.clearMeasures(...)` calls to such cases
in DEV.
Validated that entries are still shown on Performance panel timeline.
Stacked on #34829.
This lets you get an overview more easily when there's lots of things
like scripts downloading. Pluralized the name. E.g. `script` ->
`scripts` or `fetch` -> `fetches`.
This only groups them consecutively when they'd have the same place in
the list anyway because otherwise it might cover up some kind of
waterfall effects.
<img width="404" height="225" alt="Screenshot 2025-10-13 at 12 06 51 AM"
src="https://github.com/user-attachments/assets/da204a8e-d5f7-4eb0-8c51-4cc5bfd184c4"
/>
Expanded:
<img width="407" height="360" alt="Screenshot 2025-10-13 at 12 07 00 AM"
src="https://github.com/user-attachments/assets/de3c3de9-f314-4c87-b606-31bc49eb4aba"
/>
This lets you assign a name to a Promise that's passed into first party
code from third party since it otherwise would have no other stack frame
to indicate its name since the whole creation stack would be in third
party.
We already respect the `displayName` on the client but it's more
complicated on the server because we don't only consider the exact
instance passed to `use()` but the whole await sequence and we can pick
any Promise along the way for consideration. Therefore this also adds a
change where we pick the Promise node for consideration if it has a name
but no stack. Where we otherwise would've picked the I/O node.
Another thing that this PR does is treat anonymous stack frames (empty
url) as third party for purposes of heuristics like "hasUnfilteredFrame"
and the name assignment. This lets you include these in the actual
generated stacks (by overriding `filterStackFrame`) but we don't
actually want them to be considered first party code in the heuristics
since it ends up favoring those stacks and using internals like
`Function.all` in name assignment.
The index is both used as the key and for hydration purposes. Previously
we didn't preserve the index when sorting so the index didn't line up
which caused hydration to be the wrong slot when sorted.
`isStrictModeNonCompliant` on the root just means that it supports
strict mode. It's inherited by other nodes.
It's not possible to opt-in to strict mode on the root itself but rather
right below it. So we should not mark the root as being non-compliant.
This lets you select the root in the suspense tab and it shouldn't show
as red with a warning.
This ignore a Suspense boundary from the timeline when it has no visual
representation. No rect. In effect, this is not blocking the user
experience.
Technically it could be an effect that mounts which can have a
side-effect which is visible.
It could also be a meta-data tag like `<title>` which is visible. We
could hoistables a virtual representation by giving them a virtual rect.
E.g. at the top of the page. This could be added after the fact.
This ensures that we don't scroll on changes to the timeline such as
when loading a new page or while the timeline is still loading.
We only auto scroll to a boundary when we perform an explicit operation
from the user.