A host event that fires while a remote render is in flight never reaches the remote side. The remote app silently sees fewer onChange calls than the user made keystrokes, and how many it loses depends on the browser's speed.
Found while writing the remote coverage for #3027 (PR #3078). Pre-existing — it reproduces identically before and after that fix.
Reproduction
A TextField the remote app controls, rendered through the Remote test environment (packages/remote-react-components/src/tests/lib/environments.tsx), typed into with userEvent.type(input, "correcthorsebattery") — 19 keystrokes. Then log the host input's DOM value and every value the remote-side onChange received:
| Environment |
Values the remote side received |
Local, webkit + firefox |
all 19 |
Remote, webkit |
all 19 |
Remote, firefox |
10 — c, co, corr, correc, correcth, correcthor, correcthorse, correcthorseba, correcthorsebatt, correcthorsebatter |
The host input holds the full correcthorsebattery in every case, so nothing is lost on the host — the drop is on the way to the remote side. The pattern is every other keystroke once the round trip cannot keep up, and the last one is among the losses, which is what makes it visible: the remote app's state ends one character behind what the user typed and stays there.
Not a timeout: waiting 1500ms after the last keystroke does not bring the missing calls in. They are gone, not late.
It is load-dependent, not browser-specific in principle — firefox is simply the slower of the two here. On a loaded machine webkit should lose them too.
Why it matters
The remote app is the owner of a controlled field's value. If it misses the last change, its state disagrees with what the user sees for as long as the field stays untouched — a form submit sends the stale value.
Consequence already in the tree
packages/remote-react-components/src/tests/RemoteControlledValue.browser.test.tsx (PR #3078) cannot assert that the remote side received the full text, because that assertion fails in Remote + firefox for this reason. It asserts that every value the remote side reported is a prefix of what was typed instead — which still catches the defect it is there for (an echo the host applied would produce a value that is not a prefix), but it cannot catch a dropped event. Tightening it is the acceptance criterion for this issue.
Where to look
packages/remote-core owns the connection and serialization. The suspicion is the event-handler property: the remote side sends a new onChange function on every render, and a host call that lands while that property is being replaced has nothing to call. useControlledRemoteValueProps (packages/components/src/lib/remote/useControlledRemoteValueProps.ts) is on the same path but only decides which value is sent, not whether the call arrives.
Worth checking whether this also affects non-value events (onPress during a re-render), which would make it a general remote-event defect rather than a field-value one.
A host event that fires while a remote render is in flight never reaches the remote side. The remote app silently sees fewer
onChangecalls than the user made keystrokes, and how many it loses depends on the browser's speed.Found while writing the remote coverage for #3027 (PR #3078). Pre-existing — it reproduces identically before and after that fix.
Reproduction
A
TextFieldthe remote app controls, rendered through theRemotetest environment (packages/remote-react-components/src/tests/lib/environments.tsx), typed into withuserEvent.type(input, "correcthorsebattery")— 19 keystrokes. Then log the host input's DOM value and every value the remote-sideonChangereceived:Local, webkit + firefoxRemote, webkitRemote, firefoxc,co,corr,correc,correcth,correcthor,correcthorse,correcthorseba,correcthorsebatt,correcthorsebatterThe host input holds the full
correcthorsebatteryin every case, so nothing is lost on the host — the drop is on the way to the remote side. The pattern is every other keystroke once the round trip cannot keep up, and the last one is among the losses, which is what makes it visible: the remote app's state ends one character behind what the user typed and stays there.Not a timeout: waiting 1500ms after the last keystroke does not bring the missing calls in. They are gone, not late.
It is load-dependent, not browser-specific in principle — firefox is simply the slower of the two here. On a loaded machine webkit should lose them too.
Why it matters
The remote app is the owner of a controlled field's value. If it misses the last change, its state disagrees with what the user sees for as long as the field stays untouched — a form submit sends the stale value.
Consequence already in the tree
packages/remote-react-components/src/tests/RemoteControlledValue.browser.test.tsx(PR #3078) cannot assert that the remote side received the full text, because that assertion fails inRemote+ firefox for this reason. It asserts that every value the remote side reported is a prefix of what was typed instead — which still catches the defect it is there for (an echo the host applied would produce a value that is not a prefix), but it cannot catch a dropped event. Tightening it is the acceptance criterion for this issue.Where to look
packages/remote-coreowns the connection and serialization. The suspicion is the event-handler property: the remote side sends a newonChangefunction on every render, and a host call that lands while that property is being replaced has nothing to call.useControlledRemoteValueProps(packages/components/src/lib/remote/useControlledRemoteValueProps.ts) is on the same path but only decides which value is sent, not whether the call arrives.Worth checking whether this also affects non-value events (
onPressduring a re-render), which would make it a general remote-event defect rather than a field-value one.