@@ -752,6 +752,16 @@ export const UserInput = forwardRef<UserInputHandle, UserInputProps>(function Us
752752 }
753753 } , [ onSubmit , textareaRef , resetTranscript ] )
754754
755+ /**
756+ * Adopts the textarea's DOM value into state. State and DOM can only drift
757+ * when an edit's input event is lost (programmatic edits pair setValue
758+ * synchronously) — the DOM is the user's intent.
759+ */
760+ const adoptDomValue = useCallback ( ( textarea : HTMLTextAreaElement ) => {
761+ valueRef . current = textarea . value
762+ setValue ( textarea . value )
763+ } , [ ] )
764+
755765 const handleKeyDown = useCallback (
756766 ( e : React . KeyboardEvent < HTMLTextAreaElement > ) => {
757767 if ( mentionRangeRef . current && ! e . nativeEvent . isComposing ) {
@@ -1060,13 +1070,10 @@ export const UserInput = forwardRef<UserInputHandle, UserInputProps>(function Us
10601070 // may get superseded — so edge-movement inference stays true to the DOM.
10611071 lastSelectionRef . current = { start, end }
10621072
1063- // Controlled-value reconciliation: state and DOM can only drift when an
1064- // edit's change event is lost (programmatic edits pair setValue
1065- // synchronously). The DOM is the user's intent — adopt it and let the
1066- // render rebuild the overlay; selection logic resumes on the next event.
1073+ // Reconciliation backstop for non-keyboard mutations; the render rebuilds
1074+ // the overlay and selection logic resumes on the next event.
10671075 if ( textarea . value !== valueRef . current ) {
1068- valueRef . current = textarea . value
1069- setValue ( textarea . value )
1076+ adoptDomValue ( textarea )
10701077 return
10711078 }
10721079
@@ -1117,7 +1124,7 @@ export const UserInput = forwardRef<UserInputHandle, UserInputProps>(function Us
11171124 const focusPos = textarea . selectionDirection === 'backward' ? start : end
11181125 syncMentionState ( textarea , textarea . value , focusPos )
11191126 syncSlashState ( textarea , textarea . value , focusPos )
1120- } , [ textareaRef , mentionTokensWithContext , syncMentionState , syncSlashState ] )
1127+ } , [ textareaRef , mentionTokensWithContext , adoptDomValue , syncMentionState , syncSlashState ] )
11211128
11221129 const handleInput = useCallback (
11231130 ( e : React . FormEvent < HTMLTextAreaElement > ) => {
0 commit comments