It's annoying to have to try to find where it lines up with no hints.
This way when you hover over something it should be on screen.
The strategy I went with is that it scrolls to a percentage along the
scrollable axis but the two might not be exactly the same. Partially
because they have different aspect ratios but also because suspended
boundaries can shrink the document while the suspense tab needs to still
be able to show the boundaries that are currently invisible.
Right now it's possible for things like server environments to appear
before other content in the timeline just because it's in a different
document order.
Ofc the order in production is not guaranteed but we can at least use
the timing information we have as a hint towards the actual order.
Unfortunately since the end time of the RSC stream itself is always
after the content that resolved to produce it, it becomes kind of
determined by the chunking. Similarly since for a clean refresh, the
scripts and styles will typically load after the server content they
appear later. Similarly SSR typically finishes after the RSC parts.
Therefore a hack here is that I artificially delay everything with a
non-null environment (RSC) so that RSC always comes after client-side
(Suspense). This is also consistent with how we color things that have
an environment even if children are just Suspense.
To ensure that we never show a child before a parent, in the timeline,
each child has a minimum time of its parent.
We avoid visiting the same async node twice but if we see it again we
returned "null" indicating that there's no I/O there.
This means that if you have two different Promises both resolving from
the same I/O node then we only show one of them. However, in general we
treat that as two different I/O entries to allow for things like
batching to still show up separately.
This fixes that by caching the return value for multiple visits. So if
we found I/O (but no user space await) in one path and then we visit
that path through a different Promise chain, then we'll still emit it
twice.
However, if we visit the same exact Promise that we emitted an await on
then we skip it. Because there's no need to emit two awaits on the same
thing. It only matters when the path ends up informing whether it has
I/O or not.
IO tasks can execute more than once. E.g. a connection may fire each
time a new message or chunk comes in or a setInterval every time it
executes.
We used to treat these all as one I/O node and just updated the end time
as we go. Most of the time this was fine because typically you would
have a Promise instance whose end time is really the one that gets used
as the I/O anyway.
However, in a pattern like this it could be problematic:
```js
setTimeout(() => {
function App() {
return Promise.resolve(123);
}
renderToReadableStream(<App />);
});
```
Because the I/O's end time is before the render started so it should be
excluded from being considered I/O as part of the render. It happened
outside of render. But because the `Promise.resolve()` is inside render
its end time is after the render start so the promise is considered part
of the render. This is usually not a problem because the end time of the
I/O is still before the start of the render so even though the Promise
is valid it has no I/O source so it's properly excluded.
However, if the I/O's end time updates before we observe this then the
I/O can be considered part of the render. E.g. if this was a setInterval
it would be clearly wrong. But it turns out that even setTimeout can
sometimes execute more than once in the async_hooks because each run of
"process.nextTick" and microtasks respectively are ran in their own
before/after. When a micro task executes after this main body it'll
update the end time which can then turn the whole I/O as being inside
the render.
To solve this properly I create a new I/O node each time before() is
invoked so that each one gets to observe a different end time. This has
a potential CPU and memory allocation cost when there's a lot of them
like in a quick stream.
Now that RN is only on the New Architecture, we can stop stop syncing
the legacy React Native renderers.
In this diff, I just stop syncing them. In a follow up I'll delete the
code for them so only Fabric is left.
This will also allow us to remove the `enableLegacyMode` feature flag.
<!--
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
-->
Supersedes #34951
## Summary
<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->
Fix the runtime error with named imports and make the last remaining
[Are The Types
Wrong?](https://arethetypeswrong.github.io/?p=eslint-plugin-react-hooks%400.0.0-experimental-6b344c7c-20251022)
error with `eslint-plugin-react-hooks` go away, thanks to the hint from
Andrew Branch:
- https://github.com/facebook/react/issues/34801#issuecomment-3433478810
## 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 tried adding this to `node_modules` and it fixed the failures when
importing named imports like `import { configs, meta, rules } from
'eslint-plugin-react-hooks'`:
```bash
➜ eslint-config-upleveled git:(renovate/react-monorepo) pnpm eslint . --max-warnings 0
Oops! Something went wrong! :(
ESLint: 9.37.0
file:///Users/k/p/eslint-config-upleveled/index.js:13
import reactHooks, { configs } from 'eslint-plugin-react-hooks';
^^^^^^^
SyntaxError: Named export 'configs' not found. The requested module 'eslint-plugin-react-hooks' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from 'eslint-plugin-react-hooks';
const { configs } = pkg;
at ModuleJob._instantiate (node:internal/modules/esm/module_job:228:21)
at async ModuleJob.run (node:internal/modules/esm/module_job:335:5)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:647:26)
at async dynamicImportConfig (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:186:17)
at async loadConfigFile (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:276:9)
at async ConfigLoader.calculateConfigArray (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:589:23)
at async #calculateConfigArray (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:743:23)
at async directoryFilter (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/eslint/eslint-helpers.js:309:5)
at async NodeHfs.<anonymous> (file:///Users/k/p/eslint-config-upleveled/node_modules/.pnpm/@humanfs+core@0.19.1/node_modules/@humanfs/core/src/hfs.js:586:29)
at async NodeHfs.walk (file:///Users/k/p/eslint-config-upleveled/node_modules/.pnpm/@humanfs+core@0.19.1/node_modules/@humanfs/core/src/hfs.js:614:3)
➜ eslint-config-upleveled git:(renovate/react-monorepo) pnpm eslint . --max-warnings 0
➜ eslint-config-upleveled git:(renovate/react-monorepo) # no error
```
The named imports identifiers `configs`, `meta`, and `rules` also
contain values, as a sanity check:
- https://github.com/facebook/react/pull/34951#issuecomment-3433555636
cc @poteto
<!--
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?
-->
Fix the runtime error with named imports and make the last remaining
[Are The Types
Wrong?](https://arethetypeswrong.github.io/?p=eslint-plugin-react-hooks%400.0.0-experimental-6b344c7c-20251022)
error with `eslint-plugin-react-hooks` go away, thanks to the hint from
@andrewbranch:
- https://github.com/facebook/react/issues/34801#issuecomment-3433478810
## 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 tried adding this to `node_modules` and it fixed the failures when
importing named imports like `import { configs, meta, rules } from
'eslint-plugin-react-hooks'`:
```bash
➜ eslint-config-upleveled git:(renovate/react-monorepo) pnpm eslint . --max-warnings 0
Oops! Something went wrong! :(
ESLint: 9.37.0
file:///Users/k/p/eslint-config-upleveled/index.js:13
import reactHooks, { configs } from 'eslint-plugin-react-hooks';
^^^^^^^
SyntaxError: Named export 'configs' not found. The requested module 'eslint-plugin-react-hooks' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from 'eslint-plugin-react-hooks';
const { configs } = pkg;
at ModuleJob._instantiate (node:internal/modules/esm/module_job:228:21)
at async ModuleJob.run (node:internal/modules/esm/module_job:335:5)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:647:26)
at async dynamicImportConfig (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:186:17)
at async loadConfigFile (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:276:9)
at async ConfigLoader.calculateConfigArray (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:589:23)
at async #calculateConfigArray (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:743:23)
at async directoryFilter (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/eslint/eslint-helpers.js:309:5)
at async NodeHfs.<anonymous> (file:///Users/k/p/eslint-config-upleveled/node_modules/.pnpm/@humanfs+core@0.19.1/node_modules/@humanfs/core/src/hfs.js:586:29)
at async NodeHfs.walk (file:///Users/k/p/eslint-config-upleveled/node_modules/.pnpm/@humanfs+core@0.19.1/node_modules/@humanfs/core/src/hfs.js:614:3)
➜ eslint-config-upleveled git:(renovate/react-monorepo) pnpm eslint . --max-warnings 0
➜ eslint-config-upleveled git:(renovate/react-monorepo) # no error
```
The named imports identifiers `configs`, `meta`, and `rules` also
contain values, as a sanity check:
- https://github.com/facebook/react/pull/34951#issuecomment-3433555636
cc @poteto
<!--
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?
-->
Resolve the type error with the types, according to [Are the types
wrong?](https://arethetypeswrong.github.io/?p=eslint-plugin-react-hooks%407.0.0),
as an additional
- Last attempt: https://github.com/facebook/react/pull/34746
- Original issue: https://github.com/facebook/react/issues/34745
## How did you test this change?
I edited `node_modules/eslint-plugin-react-hooks/index.d.ts` in my
`"module": "Node16"` + `"type": "module"` project and my error went
away:
- https://github.com/facebook/react/issues/34801#issuecomment-3433053067
cc @poteto @michaelfaith @andrewbranch
<!--
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.
-->
## Summary
This PR updates getChangedHooksIndices to account for the fact that
useSyncExternalStore internally mounts two hooks, while DevTools should
treat it as a single user-facing hook.
It introduces a helper isUseSyncExternalStoreHook to detect this case
and adjust iteration so the extra internal hook is skipped when counting
changes.
Before:
https://github.com/user-attachments/assets/0db72a4e-21f7-44c7-ba02-669a272631e5
After:
https://github.com/user-attachments/assets/4da71392-0396-408d-86a7-6fbc82d8c4f5
## How did you test this change?
I used this component to reproduce this issue locally (I followed
instructions in `packages/react-devtools/CONTRIBUTING.md`).
```ts
function Test() {
// 1
React.useSyncExternalStore(
() => {},
() => {},
() => {},
);
// 2
const [state, setState] = useState('test');
return (
<>
<div
onClick={() => setState(Math.random())}
style={{backgroundColor: 'red'}}>
{state}
</div>
</>
);
}
```
This eliminates the gap in a reproducer for the React DevTools browser
extension from the source code that we submit to Firefox extension
stores.
We use the commit hash as part of the Backend version, here:
2cfb221937/packages/react-devtools-extensions/utils.js (L26-L38)
The problem is that we archive the source code for Mozilla extension
store reviews and there is no git. But since we still download the React
sources from the CI, we could reuse the hash from `build/COMMIT_HASH`
file.
This has been causing some issues with the submission review on Firefox
store: we use OS-level paths in these source maps, which makes the build
artifact different from the one that's been submitted.
Also saves ~100Kb for main.js artifact.
This is an aesthetic thing. Most simple I/O entries are things like
"script", "stylesheet", "fetch" etc. which are all a single word and
lower case. The "RSC stream" name sticks out and draws unnecessary
attention to itself where as it's really the least interesting to look
at.
I don't love the name because I'm not sure how to explain it. It's
really mainly the byte size of the payload itself without considering
things like server awaits things which will have their own cause. So I'm
trying to communicate the download size of the stream of downloading the
`.rsc` file or the `"rsc stream"`.
This shows the title in the top corner of the rect if there's enough
space.
The complex bit here is that it can be noisy if too many boundaries
occupy the same space to overlap or partially overlap.
This uses an R-tree to store all the rects to find overlapping
boundaries to cut the available space to draw inside the rect. We use
this to compute the rectangle within the rect which doesn't have any
overlapping boundaries.
The roots don't count as overlapping. Similarly, a parent rect is not
consider overlapping a child. However, if two sibling boundaries occupy
the same space, no title will be drawn.
<img width="734" height="813" alt="Screenshot 2025-10-19 at 5 34 49 PM"
src="https://github.com/user-attachments/assets/2b848b9c-3b78-48e5-9476-dd59a7baf6bf"
/>
We might also consider drawing the "Initial Paint" title at the root but
that's less interesting. It's interesting in the beginning before you
know about the special case at the root but after that it's just always
the same value so just adds noise.
When you use the `createFromFetch` API we assume that the start time of
the request is the same time as when you call `createFromFetch` but in
principle you could use it with a Promise that starts earlier and just
happens to resolve to a `Response`.
When you use `createFromReadableStream` that is almost definitely the
case. E.g. you might have started it way earlier and you don't call
`createFromReadableStream` until you get the headers back (the fetch
promise resolves).
This adds an option to pass in the start time for debug purposes if you
started the request before starting to parse it.
When you create a snapshot from an AsyncLocalStorage in Node.js, that
creates a new bound AsyncResource which everything runs inside of.
3437e1c4bd/lib/internal/async_local_storage/async_hooks.js (L61-L67)
This resource is itself tracked by our async debug tracking as I/O. We
can't really distinguish these in general from other AsyncResources
which are I/O.
However, by default they're given the name `"bound-anonymous-fn"` if you
pass it an anonymous function or in the case of a snapshot, that's
built-in:
3437e1c4bd/lib/async_hooks.js (L262-L263)
We can at least assume that these are non-I/O. If you want to ensure
that a bound resource is not considered I/O, you can ensure your
function isn't assigned a name or give it this explicit name.
The other issue here is that, the sequencing here is that we track the
callsite of the `.snapshot()` or `.bind()` call as the trigger. So if
that was outside of render for example, then it would be considered
non-I/O. However, this might miss stuff if you resolve promises inside
the `.run()` of the snapshot if the `.run()` call itself was spawned by
I/O which should be tracked. Time will tell if those patterns appear.
However, in cases like nested renders (e.g. Next.js's "use cache") then
restoring it as if it was outside the parent render is what you do want.
Stacked on #34906.
Infer name from stack if it's the generic "lazy" name. It might be
wrapped in an abstraction. E.g. `next/dynamic`.
Also use the function name as a description of a resolved function
value.
<img width="310" height="166" alt="Screenshot 2025-10-18 at 10 42 05 AM"
src="https://github.com/user-attachments/assets/c63170b9-2b19-4f30-be7a-6429bb3ef3d9"
/>
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
## 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 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.
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.