21611 Commits
Author SHA1 Message Date
zandGitHub b3ce115f9b Delete upstream workflows
This fork does not ship upstream's releases, and its hosted-runner jobs
never start here.

Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
2026-09-24 10:02:21 -07:00
zeekay 2e879a6067 ci: canonical pair — hanzo.yml + forge caller
github.com has no runner for hanzo-build-linux-amd64, so a caller under
.github/workflows is a gate that can never be scheduled. This is the ~7-line
caller on the plane that can (git.hanzo.ai git-runner fleet), pinned @v1.
2026-08-06 03:55:48 -07:00
Sebastian "Sebbie" SilbermannandGitHub efe2123ef4 [Flight] Port ReplyServer traversal guards to FlightClient (#37144)
Additional defense-in-depth in case consumers pass untrusted input into
Flight Client.

Flight Client generally assumes trusted input.

We'll reserve these kind of fixes for Flight Client in case the
untrusted input leads to catastrophic vulnerabilities e.g. prototype
pollutions that can be used for remote code executions.
2026-07-29 18:17:33 -04:00
Minh VuandGitHub 174ea4a275 [DOM] Blur focused descendants in Fragment refs (#37125)
## Summary

`FragmentInstance.blur()` only matched the active element against the
first level of host children. If the focused element was nested inside
one of those children, `focus()` could reach it but `blur()` would leave
it focused.

This treats an active element contained by a Fragment host child as part
of the Fragment and blurs the active element itself. It also adds
regression coverage for a nested input.

Fixes #37124.

## How did you test this change?

- `yarn test ReactDOMFragmentRefs-test --runInBand` (65 tests passed)
- `yarn test --prod ReactDOMFragmentRefs-test --runInBand` (65 tests
passed)
- `yarn prettier`
- `yarn linc`
- `yarn flow dom-node`
2026-07-29 12:21:37 -04:00
danandGitHub fb1b84f747 [Fiber] Fix hang when updating a dehydrated boundary inside a hidden tree (#37135)
Closes https://github.com/vercel/next.js/issues/95848.

Fixes a hang: if an update changes what's inside a server-rendered
Suspense or Activity boundary before that boundary has hydrated, and the
affected content is hidden, React stops committing. The new content
renders once its data arrives, but the render is discarded every time,
nothing is scheduled, and nothing ever pings — the update never lands
and the app appears frozen.

"Hidden" means either of two things, and there's a test for each:

- the update itself hides a dehydrated `<Activity>` (while mounting new
sibling content that suspends), or
- the dehydrated boundary is inside the primary tree of a parent
boundary that just suspended and is showing its fallback.

This is how we found it in practice.

With https://github.com/vercel/next.js/pull/95682, pressing Back before
hydration finishes made the router replay the missed navigation from its
first effect. This worked fine outside of Cache Components, but in Cache
Components mode (which turns on Activity), the old page's Activity
(still dehydrated) gets hidden, the new page's content suspends inside
the layout's Suspense boundary, and after the data arrives the page
stays blank forever.

As a result, https://github.com/vercel/next.js/pull/95682 got reverted.
If we fix this, we can unrevert it.

## Why it happens

When an update changes a dehydrated boundary, we schedule a render at a
higher priority to hydrate it before the update applies. If we already
tried that, we give up and client render, but mark the render as
suspended so it doesn't commit while the hydration attempt might still
finish first.

Both steps assume the attempt can actually run. Inside a hidden tree it
can't, because updates in hidden trees are deferred until the tree is
revealed. The scheduled attempt never runs but still consumes the retry
lane, which sends every later render into the give-up path — and the
give-up path keeps discarding finished renders, waiting for a hydration
attempt that isn't in flight. Once the last piece of data resolves
there's nothing left to ping us awake. The root ends up with
`pendingLanes === suspendedLanes`, `pingedLanes` empty, and no callback
scheduled.

The update doesn't need to be sync or discrete: a plain setState from an
effect is enough. Wrapping the same update in `startTransition` avoids
it, which is probably why this went unnoticed.

## The fix

If the boundary is inside a hidden tree (`isCurrentTreeHidden()`), skip
the hydration attempt and client render right away. There's nothing
visible to protect: replacing hidden server HTML doesn't show, and the
replacement children render when the tree is revealed.

One behavior note: this discards the hidden server HTML instead of
preserving it for later hydration on reveal, same as the existing
give-up path. Keeping it dehydrated and hydrating at reveal would be a
nicer follow-up, but needs commit-phase support that doesn't exist
today.

## How did you test this change?

The first commit adds failing tests for both boundary types; the fix
makes them pass. `startTransition` variants of the same scenarios are
included as passing controls. Ran the Activity, partial/selective
hydration, Fizz, Suspense, and Offscreen suites in both release
channels.
2026-07-28 23:18:44 +01:00
Alex HuntandGitHub d77b0baa1d [RN] Update ReactPrivate and InitializeCore imports (0.87 Strict API) (#36986)
## Summary

Migrate internal `Libraries/` import subpaths from `react-native` to the
incoming dedicated entry points in React Native 0.87.

**Motivation**

See [**RFC0894: Removing deep imports from
react-native**](https://github.com/react-native-community/discussions-and-proposals/pull/894)

**Changes**

The `react-private-interface` entry point has been created explicitly
for the private contract between React ↔ React Native, and is 1:1 with
the previous `ReactNativePrivateInterface` module.

Both of the below `Libraries/` paths still exist, but are deprecated —
this is a lagging migration that will enable cleanup in a future RN
version.

- `react-native/Libraries/Core/InitializeCore` →
`react-native/setup-env`
[react/react-native#57475](https://github.com/react/react-native/pull/57475)
- `react-native/Libraries/ReactPrivate/ReactNativePrivateInterface` →
`react-native/react-private-interface`
[react/react-native#57495](https://github.com/react/react-native/pull/57495)

## Test Plan

Flow
2026-07-28 16:45:37 +01:00
e271041fe5 [Flight] Define Flight chunk .then with Object.defineProperty (#37109)
[Secure Ecmascript](https://github.com/tc39/proposal-ses) would freeze
the prototype of intrinsics. Since `ReactPromise` inherits the prototype
from `Promise`, it also copies over the writable definition.

Using `defineProperty` on an inherited property is compatible with SES
though. That's also closer to how classes are specced in JS.

---------

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-27 15:30:19 -04:00
Josh StoryandGitHub e89243fe69 [Fiber] Don't reaquire HostSingletons during dev effect validation (#37113)
Stacked on #37112

In dev effects are validated using a double-invoke technique. To do this
the effects are destroyed and recreated during a validation traversal.
However this same disappear and reappear path is used when Offscreen
Fibers go hidden and HostSingletons have unique behavior when going
hidden. So now that the double invoke effects process happens during
hydration it is more common to have your Singletons be released and
acquired during this validation phase which is observable most notably
by having extra attributes removed from them. The prior commits in this
stack deal with preserving non-react owned attributes on release however
it is semantically incorrect to release and acquire the singleton during
this validation because it isn't really an effect, it simply lives in
this traversal to avoid having to do another traversal during the
commit.

This change adds a bit of info to the release and acquire path to only
conditionally perform the necessary reacquire flow if we are not in the
validation phase.
2026-07-27 09:15:00 -04:00
5ce05eb37c [Bench] Add JSON output and declare missing dependency (#37127)
Things I found while integrating this bench into
https://github.com/vercel/next.js/pull/96234.

See code.

---------

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-26 10:55:28 +01:00
Josh StoryandGitHub d4d84103e8 [Fiber] only remove properties from singletons on release (#37112)
Removing attributes does not actually reset all property state on the
singleton instance. It also has the side effect of wiping any 3rd party
set attributes and properties that 3rd party scripts and extensions.

Reimplements the singleton release to clear properties on the instance
based on the last committed props for the singleton fiber.

Notably there is an unfixed path with preamble contribution markers that
still just clears based on attributes which covers SSR'd attributes that
need to be wiped before client recovery on hydration can continue. There
are gated failing tests for this as a TODO.
2026-07-24 14:38:07 -07:00
Ruslan LesiutinandGitHub f4354188f5 [DevTools] Buffer Bridge messages during extension reconnects (#37075)
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.
2026-07-23 10:39:16 +01:00
Ruslan LesiutinandGitHub 0cbb5c38d5 [DevTools] Validate Store operation invariants (#37050)
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.
2026-07-23 10:39:16 +01:00
Ruslan LesiutinandGitHub 3025d58379 [DevTools] Harden Bridge and Wall lifecycle types (#37049)
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.
2026-07-23 10:39:15 +01:00
Ruslan LesiutinandGitHub b2ce8a5aa1 [DevTools] Shut down standalone Bridge on socket close (#37076)
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.
2026-07-23 10:39:15 +01:00
Ruslan LesiutinandGitHub 561425c9c9 [DevTools] Type EventEmitter error handling (#37048)
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.
2026-07-23 10:39:15 +01:00
e4dc33d9eb [react-server-dom-turbopack] Support experimental array format for chunks (#37095)
Turbopack has introduced a new format for chunks (experimental):

```typescript
type ChunkUrlOrMerged = ChunkUrl | [ChunkUrl, ChunkPath[], number[]]
```

This will allow us to dynamically choose to merge / unmerge chunks based
on already loaded chunks. See
https://github.com/vercel/next.js/pull/95261 for more context behind
these feature.

However, we noticed that this breaks `prepareDestinationWithChunks()` on
server-side renders. The prepared chunk script tags were a stringified
version of this array:

<img width="1758" height="474" alt="Screenshot 2026-07-22 at 9 37 06 am"
src="https://github.com/user-attachments/assets/fa273b44-5942-4037-b955-4338472242fe"
/>

Instead, we should prepare the merged chunk as these SSR-d pages would
not have any loaded chunks tracked in memory (that happens on the
client).

This PR brings back this optimisation and also would remove the
console-logged errors associated with them.


---------

Co-authored-by: Sebastian "Sebbie" Silbermann <[email protected]>
2026-07-22 23:56:50 +02:00
Tim NeutkensandGitHub 13b8719923 [Flight] Limit fake JSX call site stacks to 10 frames (#37086) 2026-07-22 12:29:45 +02:00
1e3657feae [FlightReply] Performance improvements when decoding (#37090)
Security Patches included in 19.2.8

Co-authored-by: Sebastian Sebbie Silbermann <[email protected]>
2026-07-21 09:26:12 -07:00
ce0ca3ecd0 [DOM] Handle blur on Fragments below Document (#37062)
Co-authored-by: Hanzo Dev <[email protected]>
2026-07-21 10:26:07 +02:00
Sophie AlpertandGitHub ca4c265151 [Fiber] Detect useSyncExternalStore mutations missed while Activity tree was hidden (#36947)
Fixes #27670.

When an Activity subtree is hidden, its passive effects are
disconnected, which unsubscribes from the store. On reveal, the effects
are reconnected by replaying the fiber's effect list without a render,
but if `updateStoreInstance` was not in the effect list then the
component would be left stale. This happened both when a layout effect
mutated the store during the reveal commit (after the subtree rendered
but before it resubscribed) and when the store changed while hidden and
the component bailed out of rendering during the reveal.

We now push the updateStoreInstance effect unconditionally but tag it
with HookHasEffect only under the same conditions as before, so regular
commits skip it when nothing changed but reconnection always triggers it
so it can trigger a rerender if appropriate.
2026-07-20 23:39:09 -04:00
0477e882fd [DOM] Handle scrolling of empty Fragments below containers (#37061)
e.g. `render(<Fragment ref={...}>{children}</Fragment>)` where
`children` contain no Host Components.

For `Document` we just noop because some part of the document is always
in view.
For `ShadowRoot` we use its `host`.
Any other `DocumentFragment` without a `host` issues a warning because
there's nothing to scroll. This matches the existing warning when you
call `scrollIntoView(false)` on an empty Fragment without siblings.

Notably this also applies to `<html><body /></html>` in `children`
because we skip `HostSingleton` at the moment.

---------

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-20 22:40:57 +02:00
Jack PopeandGitHub 3287424744 [Fizz] Support nested enter/exit ViewTransition animations (#36917)
Adds SSR support for nested parentEnter/parentExit View Transitions.
Fizz now emits vt-parent-enter/vt-parent-exit annotations during
streaming, and the client picks them up on hydration, so nested
enter/exit animations work for Suspense reveals.
2026-07-19 15:59:13 -04:00
fe4075812c [DOM] Scroll to text siblings of empty Fragments instead of the parent (#37060)
When a Fragment has no children, React would consider scrolling to
siblings first and then to parents. However, React only considered
`HostComponent` for the siblings.

Since we already have a heuristic for scrolling to `HostText`, we can
reuse that same heuristic.

---------

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-19 21:41:37 +02:00
Jack PopeandGitHub 25a2a2926e [Fizz] Extend stack overflow recovery to retries (#36977)
Ran into this test failure as part of
https://github.com/react/react/pull/36917 - it seems that the added code
was just enough to increase stack size and fail the deep tree recovery
test in CI. Looking into that, there appears to be a gap here with
retries, including a TODO test case for the scenario.

Fizz recovers from stack overflows in extremely deep trees by catching
the first overflow in the `renderNode` trampoline and spawning a
continuation task. That continuation is retried via `retryRenderTask →
retryNode`, which has no trampoline above it. So if the remaining tree
still doesn't fit in one fresh stack, the overflow was treated as a
fatal error instead of recovering again.

This fix re-schedules the task when a retried render overflows but
`task.node` advanced (proving forward progress was made). If this is a
real in-component overflow, `task.node` doesn't advance and we still
fail.

The existing test used `n={1000}`, which only required one recovery
round and didn't catch this gap in source mode. It's updated to
`n={1200}`, which reliably requires multiple recovery rounds.
2026-07-19 13:13:40 -04:00
Ruslan LesiutinandGitHub 34d960e910 [react-devtools-cdt-mcp] Make it esm-only package (#36973)
The package was CJS-only, now adding basic ESM package exports to make
sure it works there as well.
2026-07-16 20:00:25 +01:00
Ruslan LesiutinandGitHub 4b1e6f3962 [react-devtools-cdt-mcp] Add explicit register entry (#36972)
We should provide a way for consumers to handle the installation on
their own, so the index entrypoint will just export the APIs and won't
have any side-effects.

The `/register` entrypoint (via `import
react-devtools-cdt-mcp/register`), will subscribe an event listener and
install the tools.
2026-07-16 18:15:56 +01:00
Ruslan LesiutinandGitHub 10b6cbd3d6 [react-devtools-cdt-mcp] Make registration idempotent (#36971)
Adds support for the scenario, when the package is imported multiple
times for the same target.

This will return the same (referentially) facade object with tools, and
will only clean up if all invocations were cleaned up.
2026-07-16 17:56:51 +01:00
9891c8ff8b [Fiber] Fix false-positive hydration mismatch on nonce attributes (#37030)
Co-authored-by: Suneil Nyamathi <[email protected]>
2026-07-16 18:54:10 +02:00
Ruslan LesiutinandGitHub 3bbc18fa9c [react-devtools-cdt-mcp] Throw on import from unsupported environment (#36975)
This add an additional validation that the package is imported in a
browser-like environment. If not, we should be explicit with the user
and throw an error.
2026-07-16 14:42:37 +01:00
Ruslan LesiutinandGitHub 114e368014 Enable enableEffectEventMutationPhase everywhere (#37039)
Enable it first, will be removed later in
https://github.com/react/react/pull/37013, once it gets to stable.

See https://github.com/react/react/pull/35548 for context on the gated
changes.
2026-07-16 14:21:39 +01:00
73fe92bf27 [devtools] Document that Store consistency throws must not be worked around (#37035)
Co-authored-by: Hanzo Dev <[email protected]>
2026-07-16 15:04:50 +02:00
7623c8a446 [test] Add Flight regression test for async debug info surviving Promise GC (#37037)
Co-authored-by: Hanzo Dev <[email protected]>
2026-07-16 14:50:17 +02:00
Rubén NorteandGitHub c49275ba14 Clean up flag to enable microtasks in RN (#37021)
## Summary

Microtasks are enabled by default in RN already, so there's no need to
gate their use in Fabric atm.

## How did you test this change?

Existing tests. Tested e2e at Meta.
2026-07-15 13:21:31 +01:00
Saransh JainandGitHub 82e730d8d2 [DevTools] Don't reconnect proxy port while page is prerendering (#37009) 2026-07-14 11:04:45 +02:00
Sophie AlpertandGitHub 3596bf4463 [Fiber] Update input.defaultValue for type=number too (#36980)
We used to avoid updating .defaultValue until blur on number inputs
because of a Chrome bug, but the relevant Chromium behavior was fixed in
2020 in M85 https://issues.chromium.org/issues/40124871 and likewise in
WebKit https://bugs.webkit.org/show_bug.cgi?id=217156 so I think we can
probably just remove this special-cased logic.

We do still need some special logic for number inputs because it's the
only type where we let users specify a non-string value that has
multiple string representations and need to be careful not to clobber
the .value if it has a slightly different string than what you'd get
from casting a number to string.

Fixes #29862.

Tested with the number inputs fixture in current Chrome, Safari, and
Firefox.
2026-07-09 19:58:34 -07:00
Sophie AlpertandGitHub de89c17390 [Fast Refresh] Find and remount wrapper edits behind lazy() (#36965)
Two bugs:

- Type changes to a lazy's inner component (e.g., adding a `memo`
  comparison function) were missed by the refresh scan.
- If a SimpleMemoComponent needs to be remounted, previously
  resolveRemountTypeForHotReloading would return the inner function
  and we would incorrectly recreate the fiber as a FunctionComponent.
  Instead, we can just use elementType consistently now that the
  beginWork functions consistently call resolveFamily on the wrapped
  components.
2026-07-08 09:59:07 -07:00
Sophie AlpertandGitHub c72b00e783 [Fast Refresh] Make edits to a memo comparison function take effect (#36964)
Editing the second argument of memo() previously never took effect
until something else remounted the tree, for two separate reasons:

- When adding a comparison function, we need to switch from
  SimpleMemoComponent to MemoComponent so canPreserveStateBetween
  should return false.
- MemoComponent was missing from the hot reload type resolution in
  createWorkInProgress, so existing fibers kept reading .compare from
  the old memo object forever.

New behavior:

- Adding or removing the comparison function remounts; we need to do
  this when adding (SimpleMemoComponent doesn't support a comparison
  function) so let's also do it when removing.
- Editing the comparison function implementation applies in place
  with state preserved

The TODO in isCompatibleFamilyForHotReloading is removed as that
wasn't the right place to do this check.
2026-07-08 09:58:35 -07:00
Sophie AlpertandGitHub 670d402ccd [Fast Refresh] Derive the fiber tag from the resolved type when mounting (#36963)
If an edit changed the kind of the type (e.g. a plain function got
wrapped in memo()), the fiber could get the old kind's tag with the
new kind's type, usually throwing an error.
2026-07-08 09:56:38 -07:00
Sophie AlpertandGitHub 1fb25ae06a [Fast Refresh] Unify hot reload type resolution (#36962)
Previously resolveForwardRefForHotReloading was treated as a special
case. It's simpler to move this logic to updateForwardRef which
mirrors how updateMemoComponent and mountLazyComponent already are,
and it allows us to unify to a single resolveTypeForHotReloading which
we'll use in the next commit.

Since .type is no longer changing we need to set didReceiveUpdate
manually.

This behavior is already tested in "can update forwardRef render
function in isolation".
2026-07-08 09:11:20 -07:00
ff48978c00 [test] Fix Error Proxy stack assignment in Node.js 21+ (#36967)
Same as #35227, but for `set`.

V8's `stack` setter silently noops when `this` is a Proxy
(nodejs/node#60862), so prodstack-stripping assignments like
`error.stack = 'Error: ' + message` in `resolveErrorProd` were dropped
and reads returned the real stack.

This caused `yarn test --prod` failures on Node 21+.

## Test Plan

`yarn test --prod` passes on both old and new Nodes.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-08 13:33:19 +01:00
2a784bedd2 [Fixture] flight-ssr-bench: drain immediates between iterations to fix false memory leak (#36968)
Claude found this while running the Flight/Fizz bench.

---

While measuring memory in this fixture, the Flight server appeared to
leak ~140KB per render: `heapUsed` grew steadily across hundreds of
renders even with forced GC, and roughly 3x worse when a Flight client
consumed the stream in the same process. Fizz-only runs were flat, so it
looked like a real Flight leak.

On a closer look, the benchmark loop seems to be lying. React schedules
one `setImmediate` per request, but every render in this fixture
completes entirely in promises and `nextTick` callbacks, so a tight
benchmark loop never gives the event loop a chance to run immediates at
all. They quietly pile up, and each pending callback keeps its
already-finished request alive.

Heap snapshots show that every "leaked" request was held by a pending
immediate, and nothing else. Adding a single `await setImmediate` per
iteration makes memory completely flat over 300 renders, with or without
a same-process Flight client. A real server yields to the event loop on
every request, so this can't happen outside a synthetic loop.

The fix is to yield to the event loop between benchmark iterations,
outside the timed window, so latency numbers are unaffected. Each
variant now also reports how much heap it retains after the run settles.
That number was meaningless before — the pile-up made variants with
bigger per-request graphs look like they used more memory when they
didn't.

After the fix, a full `yarn bench:bare` run retains +0.3–0.6MB total per
variant after 1000 iterations (JIT/warmup noise), instead of drifting by
hundreds of MB.

---------

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-08 13:31:23 +01:00
9277edc43a [Flight] Recognize Node 22+ V8 frames for Promise statics in debug info (#36969)
## Summary

Newer V8 versions (Node.js ≥ ~22) name the stack frames of static
methods on the `Promise` constructor `Promise.all`, `Promise.race`,
etc., where older versions named them `Function.all`, `Function.race`,
etc. The `isPromiseCreationInternal` and `isPromiseAwaitInternal`
allowlists in `ReactFlightServer` only matched the old spellings.

## How did you test this change?

- `yarn test --no-watchman ReactFlightAsyncDebugInfo` — 18/18 pass on
both Node 20.19.0 and Node 24.16.0 (previously, "can track async
information when awaited" failed on Node 24)
- `yarn test --silent --no-watchman packages/react-server/src/__tests__
packages/react-server-dom-webpack` — 243/243 pass on both Node versions,
default and `-r=experimental` channels
- `yarn test-www --silent --no-watchman
packages/react-server/src/__tests__` with `__VARIANT__` true and false —
pass
- `yarn flow dom-node`, `yarn prettier`, `yarn linc` — pass

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-08 13:02:09 +01:00
5894c3a0be [compiler] Bail out with Todo on using and await using declarations (#36946)
## Summary

The compiler silently rewrote functions containing `using` and `await
using` declarations (explicit resource management). BuildHIR's
`VariableDeclaration` case only special-cases `var`; every other kind
falls through to `InstructionKind.Const`. A `using` declaration
therefore compiled as a plain memoized `const`, and the implicit dispose
call at scope exit was dropped from the compiled output. No diagnostic
was emitted. The Rust port mirrored the same bug in `build_hir.rs`.

The Rust port had a second, harder failure: its
`VariableDeclarationKind` enum had no `await using` variant, so a Babel
AST containing one failed serde deserialization at the NAPI boundary
("unknown variant `await using`") and threw for the whole file.

The fix records the same per-function Todo bail as the existing `var`
case, in both implementations, then continues lowering the declaration
as `const` so references do not break while the error unwinds (mirroring
how `var` continues as `let`). This matches the compiler's established
Todo-bail precedent for unsupported syntax: the function is skipped with
a logged `CompileError` diagnostic, `panicThreshold` is respected, and
sibling functions in the same file still compile. This deliberately
contrasts with oxc-project/oxc#24217, which handles the same syntax with
a silent per-function skip; nothing in this compiler is skipped without
a surfaced diagnostic.

Changes:

- TS: `BuildHIR.ts` records a Todo `CompilerErrorDetail` for `using` and
`await using` kinds, in the style of the existing `var` case.
- Rust: same Todo `record_error` in `build_hir.rs`. New `AwaitUsing`
variant (serde name `"await using"`) in `react_compiler_ast`, so `await
using` survives the NAPI boundary, covered by a serde round-trip unit
test.
- Test harness: enables the `explicitResourceManagement` parser plugin
in snap and in the e2e script's Babel baseline so fixtures can use the
syntax.

Fixtures (identical snapshots for the TS and Rust pipelines):

- `error.todo-using-declaration` and
`error.todo-await-using-declaration` show the Todo diagnostic with code
frames.
- `using-declaration-bailout-sibling-compiles` shows the bailing
function left untouched (disposal preserved) while a sibling component
in the same file is memoized.

The first commit snapshots the previous broken behavior (`using`
memoized as `const` with disposal dropped); the second commit lands the
fix and updates the snapshots.

## How did you test this change?

All results below are from the branch rebased onto current main.

TS implementation:

- `yarn snap`: 1809 tests, 1809 passed, 0 failed.
- `yarn workspace babel-plugin-react-compiler lint`: clean. `yarn
prettier-check` at the repo root: clean.
- Manually verified pre-fix output: a component with `using resource =
getResource(props.id)` compiled to a memoized `const` with no disposal.

Rust implementation:

- `cargo test --workspace`: 42 passed, 0 failed (includes the new serde
round-trip unit test for both `using` kinds).
- `bash scripts/test-babel-ast.sh`: passed.
- `bash scripts/test-rust-port.sh`: 1808 passed, 0 failed.
- `yarn snap --rust`: 1809 tests, 1809 passed, 0 failed, against the
same `.expect.md` snapshots as the TS run.
- `scripts/test-e2e.ts` (babel variant): the three using fixtures pass
code and events parity.
- Manually verified `await using` now deserializes across the NAPI
boundary and records the Todo instead of throwing.

<div><a
href="https://cursor.com/agents/bc-5fe4350d-5d42-4c0e-9001-429ed0b5e5b7"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-5fe4350d-5d42-4c0e-9001-429ed0b5e5b7"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

---------

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-07 20:46:37 -07:00
d665a321a8 [Fast Refresh] Remount correctly when an edit changes the component kind (#36950)
Fixes #30659. I'm not confident in this yet but here's what Fable said
based on
https://github.com/react/react/pull/32214#issuecomment-2734436781:

Editing a component from a plain function to a memo or forwardRef
wrapper (or between wrapper kinds) crashed with "Component is not a
function", because canPreserveStateBetween only compared hook
signatures. The edit was classified as state-preserving, so
createWorkInProgress swapped the wrapper object in as the type of a
FunctionComponent fiber and renderWithHooks tried to call it.

Two fixes, both DEV-only:

- canPreserveStateBetween returns false when typeof or $$typeof differ.
A fiber's tag is derived from the kind of its type, so state can never
be preserved across a kind change; these edits must go to staleFamilies
(remount). Nested wrappers need no special handling because register()
creates a family per nesting level ($type/$render ids), and each level's
kind is compared against its own family.

- The reconciler now finds and remounts wrapper fibers whose outer kind
changed. scheduleFibersWithFamiliesRecursively previously resolved
families only through the inner implementation (type/type.render), but a
kind-changing edit is recorded only on the outer type's family, so edits
like memo -> function never reached the scanner and were silently
dropped. The scanner now also checks the elementType's family, and the
_debugNeedsRemount branch in beginWork rebuilds the fiber from
family.current (the latest registered type for the fiber's identity)
rather than the fiber's possibly-stale inner type. This also fixes stale
simple memo remounts recreating as plain functions, dropping the memo
wrapper.

Unlike the reverted #30660 (see #32214), this does not fabricate
families or guess type shapes: families are still created in exactly one
place, and the same ID always resolves to the same family.

Restores the tests from #30660 and adds coverage for changing the inner
type of a memo between function and forwardRef.

Co-authored-by: BIKI DAS <[email protected]>
Co-authored-by: dan <[email protected]>
2026-07-07 16:30:46 -07:00
laurenandGitHub cda292d3ff [compiler] Port JSX tag classification fix to Rust (not-lowercase is a component) (#36951) 2026-07-07 09:46:24 -07:00
Sophie AlpertandGitHub 3f19e80d4f [Fiber] Don't invoke effects on moved children in StrictMode (#36948)
Fixes #29585.
2026-07-07 09:09:44 -07:00
Sophie AlpertandGitHub e0c95af0b2 [Fiber] Don't set .innerHTML when it hasn't changed (#36949)
Fixes #30994. We compared this previously but it regressed in #26501.
2026-07-07 09:09:10 -07:00
Kurtd Daniel BigtasandGitHub b444fd4533 [DevTools] Fix WhatChanged scrolling out of view in profiler sidebar (#36244)
## Summary
Previously, WhatChanged and InspectedElementBadges were rendered inside
the same scrollable [Content
div](https://github.com/facebook/react/blob/main/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.js#L130-L147)
as the commit list. When a component had many render timestamps,
scrolling down to pick one caused the "what changed" context to scroll
out of view, requiring the user to scroll back up to see it.

While the profiler already exposes render details via hover tooltips on
fibers, those are transient and pointer-dependent. This change
introduces a sticky header section at the top of the Content div that
holds the current commit summary, badges, and WhatChanged. The section
is collapsible (click or Enter/Space) so users can reclaim vertical
space for the commit list when needed. The commit list now sits below
the sticky header as a sibling, so it scrolls independently while the
summary remains always visible.

Closes #36525 

---
### Current Behavior

https://github.com/user-attachments/assets/05f2889a-4e0d-4464-965a-2821c63c2d30

### Proposed UI
Apologies on this one. I had to [upload it on
YouTube](https://youtu.be/g3KMaqn33G0) since the video is quite big.

---

## How did you test this change?

### Manual Testing
| Case  | Screenshot |
| ------------- | ------------- |
| Expanded simple | <img width="1512" height="948"
alt="expanded-default"
src="https://github.com/user-attachments/assets/00d3bf11-ef98-463e-96ec-c918e58147ff"
/> |
| Collapsed Simple | <img width="1512" height="948"
alt="collapsed-default"
src="https://github.com/user-attachments/assets/75d0ae79-c96f-4c55-9dc1-538ba65037b8"
/> |
| Focused using tab navigation while expanded | <img width="1512"
height="948" alt="expanded-tab-focused-simple"
src="https://github.com/user-attachments/assets/c4098f2d-1c43-4b04-ab69-1e646e79bc8e"
/> |
| Focused using tab navigation while collapsed | <img width="1512"
height="948" alt="collapsed-tab-focused-simple"
src="https://github.com/user-attachments/assets/75840812-b700-44dd-b82d-51d5a867f501"
/> |
| Long render info( expanded ) | <img width="1512" height="948"
alt="long-desc-expanded"
src="https://github.com/user-attachments/assets/53c4d776-55d5-4bb0-818d-a70e032e57d7"
/> |
| Long render info( collapsed ) | <img width="1512" height="948"
alt="long-desc-collapsed"
src="https://github.com/user-attachments/assets/45eb7f6f-fcc6-4f7b-88e7-e3ece9e59d19"
/> |

#### Hover (expanded)

https://github.com/user-attachments/assets/d04668d3-1e0e-44c7-af7e-087d246ec07d

#### Hover (collapsed)

https://github.com/user-attachments/assets/86638b8d-60c2-4cfa-b357-c1104e76767e

---

## Why this matters

This is more than a visual polish issue:

* The Profiler is fundamentally a context-driven debugging tool
* Losing the “why did this render?” explanation during navigation
interrupts analysis flow
* The issue becomes progressively worse on large applications or heavily
interactive surfaces
* Hover tooltips are not an adequate replacement because they are
transient and pointer-dependent
* Keyboard users are disproportionately affected due to repeated
navigation and scrolling

In practice, this increases cognitive load during performance debugging
and makes long profiling sessions unnecessarily cumbersome.
2026-07-07 14:18:51 +01:00
DmitriiandGitHub dd1ba0ffd6 [compiler] Fix JSX tags prefixed with _ or $ incorrectly treated as host elements (#36688)
## Summary

Fixes #36601

### Problem

In `lowerJsxElementName` (BuildHIR.ts), the condition `if
(tag.match(/^[A-Z]/))` only treats JSX tags starting with an **uppercase
letter** as component references. Tags starting with `_`, `$`, or any
other non-letter character fell through to the `else` branch and were
incorrectly classified as `BuiltinTag` (host/intrinsic elements).

This meant that `<_Bar />` or `<$Foo />` was treated like `<div />`,
causing the compiler to skip memoization of the component and
potentially producing incorrect output.

### Root Cause

JSX semantics (as implemented by Babel's JSX transform) are:
- Tag starts with **lowercase** letter → host/intrinsic element (string
tag)
- **Everything else** → component reference (in-scope identifier)

The original code only handled the first half of that rule ("starts with
uppercase → component") while ignoring identifiers like `_Bar` and
`$Foo`.

### Fix

Change:
```ts
if (tag.match(/^[A-Z]/)) {
```
To:
```ts
if (!tag.match(/^[a-z]/)) {
```

This correctly classifies any JSX identifier that does NOT start with a
lowercase letter as a component reference, matching JSX spec semantics.

### Test

Added fixture `jsx-underscore-prefix-component` that renders `<_Bar />`
and verifies the compiler correctly memoizes it as a component
reference.

## How did you test this change?

- Added new compiler fixture test: `jsx-underscore-prefix-component`
- Ran `yarn workspace babel-plugin-react-compiler lint` ✅
- Ran snap tests with the new fixture to generate expected output ✅
2026-07-06 22:24:54 -07:00
gtkatakuraandGitHub efedd75011 [compiler]: add useWindowVirtualizer to known incompatible libraries (#36912)
## Summary

#34493 added `@tanstack/react-virtual`'s `useVirtualizer` to the known
incompatible libraries, but not `useWindowVirtualizer`.

Both hooks are thin wrappers around the same internal
`useVirtualizerBase`, so they return the same referentially-stable
virtualizer instance. Its methods (e.g. `getVirtualItems()`,
`getTotalSize()`) return internally-mutated values rather than new ones,
which is exactly the "interior mutability" pattern that breaks
memoization described in the module type provider.

Because only `useVirtualizer` was registered, code using
`useWindowVirtualizer` is silently compiled with the same unsafe
memoization. In our app this froze `getVirtualItems()` to its
first-render value (an empty `[]` before measurement), producing
permanently-empty virtualized lists — the same class of bug #34493
fixed, just via the window variant.

This registers `useWindowVirtualizer` alongside `useVirtualizer` with
the identical shape, in **both** implementations:
-
`compiler/packages/babel-plugin-react-compiler/src/HIR/DefaultModuleTypeProvider.ts`
-
`compiler/crates/react_compiler_hir/src/default_module_type_provider.rs`
(Rust port)

See also the community report referenced in #34493:
https://github.com/TanStack/virtual/issues/736#issuecomment-3065658277

## How did you test this change?

Each addition mirrors the existing `useVirtualizer` entry exactly (same
kind, params, return type, and `knownIncompatible` message shape); both
are data-only additions to the module type providers, following the
precedent set by #34493 which changed only the TS file. The Rust change
keeps the two providers in sync.
2026-07-06 16:54:22 -07:00