diff --git a/apps/mobile/src/features/threads/QuestionAttachments.tsx b/apps/mobile/src/features/threads/QuestionAttachments.tsx index 724a607f57d52..d98488c755dda 100644 --- a/apps/mobile/src/features/threads/QuestionAttachments.tsx +++ b/apps/mobile/src/features/threads/QuestionAttachments.tsx @@ -7,7 +7,7 @@ import { useNativePaste } from "../../lib/useNativePaste"; import { convertPastedImagesToAttachments } from "../../lib/composerImages"; import { PROVIDER_SEND_TURN_MAX_ATTACHMENTS, type UserInputQuestion } from "@t3tools/contracts"; import { useAtomValue } from "@effect/atom-react"; -import { View } from "react-native"; +import { View, type TextInputInstance } from "react-native"; import { useEffect, useEffectEvent, useRef, useState } from "react"; import { ComposerAttachmentButton } from "./ComposerAttachmentButton"; import { ComposerAttachmentStrip } from "../../components/ComposerAttachmentStrip"; @@ -64,6 +64,18 @@ export function QuestionAttachments(props: { }); // oxlint-disable-next-line t3code/no-unreasoned-state-sync -- releases keyboard ownership on unmount, since an unmounted native input never emits blur useEffect(() => () => releaseFocus(), []); + // A controlled value makes Fabric rebuild the native text on every keystroke, which discards + // iOS's pending autocorrection. The input owns its text; the only outside write is an option + // selection clearing the draft. + const inputRef = useRef(null); + const [initialText] = useState(props.value); + const nativeText = useRef(props.value); + // oxlint-disable-next-line t3code/no-unreasoned-state-sync -- pushes an option selection's cleared draft into the uncontrolled native input + useEffect(() => { + if (props.value !== "" || nativeText.current === "") return; + nativeText.current = ""; + inputRef.current?.clear(); + }, [props.value]); const append = ( key: string, attachments: Parameters[1], @@ -195,11 +207,15 @@ export function QuestionAttachments(props: { setPreviewVideo(null)} /> { + nativeText.current = text; + props.onChangeText(text); + }} onFocus={() => { focusedRef.current = true; onInputFocusChange?.(true);