You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: Native Chat structured send can resurrect a just-sent message into the next one #17359
Found reviewing #17169, with a runnable reproduction. Not a blocker for that PR — it replaces a worse bug — but it is a real regression that PR's ownership model creates, and it should not stay implicit.
Sequence
User commits a syllable and presses Enter. The structured send starts.
Before the RPC resolves, the user opens the next composition.
setDraft('') lands while a composition is active, so the layout effect skips the DOM write and the clear is silently dropped.
onImeSettled then adopts element.value as authoritative — and the already-sent text is still sitting in it.
Reproduced against the real NativeChatComposerField and the real settle contract:
dom = "안녕하" sent = ["안녕"]
The composer ends up holding the message it already sent, which then rides along on the next send.
Why it happens
NativeChatComposer.tsx:255 clears the draft asynchronously, inside dispatchNativeChatStructuredComposerText(...).then(...). The PTY path (use-native-chat-pty-composer-send.ts:105) clears synchronously and is unaffected.
The window is one RPC round trip, and it widens on remote/SSH structured sessions.
This is the direct cost of "the browser always wins at settlement," which #17169 adopts deliberately and for good reason. On main the same sequence loses the new preedit instead (issue #16911) rather than duplicating — so this is a different failure, not a strictly worse one.
Two fixes, and the obvious one is not free
Clear optimistically before awaiting, matching what the PTY path already does. But the structured path guards on if (!accepted) return and only clears on acceptance — so clearing early trades draft-resurrection for draft loss when a send is rejected. That is worse, not better.
Have the settle path not blindly adopt element.value when a programmatic clear was skipped. More surgical: record that a clear was dropped mid-composition and apply it at settlement, so the browser still wins on composition content while a programmatic clear is not silently lost. This looks like the right shape but is the larger change.
Related, from the same review
use-native-chat-composer-attachments.test.tsx:211-212 asserts the deferred flush never steals focus, but the focus call is inside requestAnimationFrame and the test never advances a frame. Mutating the flush to applyResolvedPaths(paths, true, ...) steals focus with all 50 tests still passing. A three-line rAF flush before those assertions kills the mutant and makes that PR bullet real.
Activity
pullfrog commentedon Aug 30, 2026
fix(native-chat): replay a draft clear dropped mid-composition (#17392)
fix(native-chat): replay a draft clear dropped mid-composition (stabl…