Conversation
With native:model every keystroke sends a change event and PHP re-renders once per event. When typing outpaces the round trip, several renders are in flight. The field only remembered the newest value it sent, so the echo of an older keystroke looked like a real server change and replaced whatever had been typed since. On iOS that replaced text was then sent back to PHP, so old letters kept bouncing around. Each field now keeps a short queue of values it has sent and not yet seen come back. PHP handles events in order, so an incoming value that matches the queue is an echo: drop it and everything older. A value that isn't in the queue is something PHP set on purpose, like clearing the field after Add, and is applied as before. Covers the shared iOS core and all three Android variants. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #95.
Typing fast into a text input bound with
native:modelloses letters. "Buy milk" gets saved as "Buyilk" or "Byu". It happens on iOS and Android, and debounce makes it rarer without fixing it.Each keystroke sends a change event and PHP re-renders once per event. The tree from each render carries the value from that event. When keys arrive faster than the round trip, several renders are in flight at once. The renderers only remembered the last value they sent, so the echo of an earlier keystroke didn't match. It was treated as a real server change and replaced everything typed since. On iOS it gets worse, because the replaced text fires the text change handler and is sent back to PHP. Old letters then keep bouncing for a while and bleed into the next thing you type.
The fix keeps a small queue of values each field has sent and not yet seen come back (
NativeUITextEchoTrackerin Swift,TextEchoTrackerin Kotlin). PHP handles events one at a time and in order, so when a published value matches something in the queue it's an echo. We drop it and everything before it and leave the field alone. That also works when the renderer skips some trees. A value that isn't in the queue is something PHP chose to set, like clearing the draft after Add, and it's applied the same way as before. The queue is capped at 64 so a field whose model is never published back can't grow it forever.Changed in the shared iOS core (so outlined, filled and bare) and in all three Android renderers.
This overlaps with #96. That PR drops every server value while the field is focused, apart from the keep-focus-on-submit clear on iOS. I think that would stop "Add" from clearing a focused field in the usual todo pattern, since tapping a button doesn't take focus away from the field. It also only covers the outlined variant on Android. I haven't run #96 to confirm the Add case. This PR works without looking at focus, so it doesn't have that trade-off.
Testing
I tested in a copy of super-native on the iPhone 17 simulator, on a screen with
native:model="draft", submit to add, and a 30ms render. AXe and simdeck taps and key events don't reach the app on this Xcode 27 setup. So a test-only hook in the local copy (not in this PR) appended characters to the field'stextevery 25ms, then submitted. That runs the sameonChange(of: text)and commit path a keystroke does.mainas it is: 0 of 8 words came through. "Buy milk" became "By" or "Bmi", and later words were full of letters from earlier ones.Swift typechecks (
swiftc -typecheck -wmoagainst mobile-airmain, with the same baseline errors as before and a canary to prove the file was checked). Kotlin compiles (:app:compileDebugKotlinin super-native's Android project with these files swapped in). I didn't run it on an Android device or emulator.Things to look at
🤖 Generated with Claude Code