Latest Results
ENG-12682 Let React Compiler optimize generated components (#7694)
* Let React Compiler optimize generated components
Replays #7272 onto main. That PR auto-closed when its base branch was
deleted after #7234 merged; its own changes were the diff between the
base-branch head it last merged (07aa53cb6) and its head (4f3e5dbba),
covering commits c01f4d48d, 755ddbafe and 4f3e5dbba.
Change the patterns Reflex emits that made React Compiler bail out:
register refs through a useRegisteredRef hook instead of writing to
refs during render, wrap generateUUID in an inline useMemo callback,
list only referenced bindings as useCallback dependencies, disable
function outlining, and describe Emotion's jsx() to the compiler as
JSX. Pin the experimental compiler, which accepts refs passed to jsx().
Add an auto_memoize config option (default True). False removes
MemoizeStatefulPlugin from page compilation so the compiler memoizes
whole pages; app-root memoization is unchanged.
Conflicts with main: #7586 removed hook positions, so the ref hook no
longer passes VarData(position=INTERNAL); #7435 removed
backend_state_mismatch from state.js, so it stays removed.
Claude-Session: https://claude.ai/code/session_01LwJUCTv3FVWzi1UoUpyMLT
* Tie the callback prune pattern to Python names, tidy tests and fragment
Export GENERATED_EVENT_CALLBACK from the React Compiler adapter and test it
against the names get_memoized_event_triggers produces, so a rename on either
side fails a test instead of silently restoring compiler bailouts.
Drop the Unicode stale-callback claim from the reflex-base fragment: main never
had that bug. Move inline test imports to module level.
* Run the auto_memoize compile_app test as a dry run
Without dry_run, REFLEX_SKIP_COMPILE or a leftover .web/nocompile file made
compile_app return before building the page plugins, and the test wrote into
the working directory's .web.
* Derive the client counter test's JS from the var's real identifier (#7741)
test_memo_client_counter hard-coded the JavaScript name a client state
var binds its value to ("Δcounter + 1"). Merging main brought in #6936,
which drops the name of a legacy ClientStateVar created with
global_ref=False and suffixes the emitted identifier with RxClientState,
so the hook now reads `const [cs$0RxClientState, setCs$0] = ...` while
the click handler still read `Δcounter`. That bare identifier resolved
to the button carrying id="Δcounter" through the browser's named
element access, and the counter rendered "[object HTMLButtonElement]1".
Build the increment expression from str(counter.value) instead, with a
{value} placeholder for the direct read and a {source} placeholder for
the eval'd reads, so the test follows whatever identifier Reflex emits.
Use rx.client_state(0) and .set(...) in place of the deprecated
ClientStateVar.create / set_value entry points. rx.client_state rejects
non-ASCII names, so the Unicode case becomes a plain scoped counter;
test_react_compiler_callback_dependencies still covers Unicode bindings
in the Babel dependency prune.
Claude-Session: https://claude.ai/code/session_01ESN8Dfnc4fUrUVcPsqaLGk
Co-authored-by: Claude <noreply@anthropic.com>
* Keep lifecycle events in their branch when auto_memoize is off
Without memo wrappers, on_mount and on_unmount hooks were hoisted to the
page function. A hidden rx.cond branch fired on_mount on page load, and an
rx.foreach item hook read the loop variable outside the loop.
Render the events as a MountEffect element beside the component instead.
* Warn once that react_compiler is experimental
* Render lifecycle events as elements in every compile mode
An on_mount inside rx.foreach was hoisted to the Foreach memo body, where
the loop variable does not exist, so the page crashed. Run
InlineLifecyclePlugin with auto-memoization on as well, and rewrite a
snapshot subtree before the memoize plugin seals it.
* Limit the lifecycle element plugin to the experimental options
Default apps keep the generated code they have on main. The plugin runs
with react_compiler=True or auto_memoize=False.
* Keep scope wrappers when auto_memoize is off; remove MountEffect
The MountEffect element kept one instance across an rx.cond branch switch,
so no lifecycle event fired, and its fragment hid the child from an
as_child parent.
With auto_memoize=False, still wrap a component that has a lifecycle event
or hooks of its own in a memo component. A wrapper has its own identity and
forwards props, and it keeps the hooks in their rx.cond branch.
* Test rx.foreach item lifecycle with auto_memoize off; complete the docstring
- An item's lifecycle hook compiles into a wrapper inside the loop body and
receives the loop variable when auto_memoize is off.
- default_page_plugins names the snapshot boundary and ALWAYS exceptions.
Claude-Session: https://claude.ai/code/session_01LwJUCVweoEhh1JsZA7emsa
* Keep lifecycle of prop components in a wrapper when auto_memoize is off
With auto_memoize off, a lifecycle event of a component held in a prop moved
into the page function, so it fired on page load even inside a hidden
rx.cond branch. The scope-only rule now uses _has_instance_hooks, which also
checks components held in props.
Claude-Session: https://claude.ai/code/session_01LwJUCVweoEhh1JsZA7emsa
---------
Co-authored-by: Masen Furer <m_github@0x26.net>
Co-authored-by: Claude <noreply@anthropic.com> Record a connection that cannot be attempted instead of rejecting
ensureSocketConnected is called without being awaited, by addEvents and by
the mount effect. When the connection could not even be attempted it rejected,
and nothing caught that: with site storage blocked, connect() throws a
SecurityError reading the token from sessionStorage. The unhandled rejection
reached window.onunhandledrejection, which reports errors with addEvents,
which called ensureSocketConnected again, which rejected again.
In Chromium, with sessionStorage made to throw, a running app raised about
12,000 unhandled rejections a second and went on doing so (45,128 after four
seconds), opened no socket, queued one unsendable exception event per turn,
and showed no connection error. With this change the same page raises none.
The failure is now caught and added to the connection errors, where every
other connection failure goes.
The call was one of ten promises in `src` that nothing awaited. The other
nine are deliberate, and are marked `void`. biome's noFloatingPromises is on
for what ships, so the next one is a decision rather than an accident. The
rule comes from Cloudflare's Workers best practices, where a floating promise
is dropped work; in a browser it is an error that surfaces somewhere else.
Claude-Session: https://claude.ai/code/session_01LwJUCSZCLWw5meAowrx3Qtclaude/js-package-extraction-testing-obmwr4 Lint and type-check the frontend package, and pin what it exports
The package had tests but nothing that reads the code the way a reviewer
does. Three gates now run in CI beside them (`npm run check`):
- biome lint, configured in lint.biome.jsonc. Its findings in the source are
fixed: strict equality throughout, `Array.isArray`, a radix for `parseInt`,
`Number.isNaN` in place of self-comparison, a scoped `case` declaration, and
an `Object.assign` where a reduce copied its accumulator on every event.
None of it changes behaviour.
- tsc over the JSDoc (`checkJs`), with nothing emitted, so there is still no
compile step between the package and an app. types/globals.d.ts declares
what the runtime reads that no module defines: the hooks other code puts on
`window`, the per-app env.json, Vite's `import.meta.hot`. An undeclared
global now fails the check.
- tests/public_api.test.js lists the names each module exports, since compiled
pages and separately released component packages import them by name, and
imports every module in node, where a production build prerenders pages.
state.js evaluates event code with `eval`, which resolves names from the
scopes around it. The linter's unused-variable rules are off for that file
for this reason, with the reason written next to the setting, and the README
gains a section on this and the rest of the state that does not appear in
function signatures.
The config is not named biome.jsonc: the repository's format hook runs biome
from the root without a configuration and stops when it finds a nested one.
Claude-Session: https://claude.ai/code/session_01LwJUCSZCLWw5meAowrx3Qtclaude/js-package-extraction-testing-obmwr4 Latest Branches
-5%
claude/js-package-extraction-testing-obmwr4 -5%
farhan/react-compiler-generation-revive -5%
farhan/foreach-scope-vars © 2026 CodSpeed Technology