From da78de3a214a441015e6c0506a9655bf38810f4c Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Sun, 5 Jul 2026 02:32:40 +0300 Subject: [PATCH] fix(chat): send preset starters directly instead of staging them in the composer Starter chips route their text into the submit as an explicit override; the previous flow staged it in the textarea, which doesn't exist while the mobile composer is collapsed into the pill, so the submit read an empty snapshot and silently bailed, leaving the command text sitting in the input. --- packages/ui/src/components/chat/ChatInput.tsx | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/ui/src/components/chat/ChatInput.tsx b/packages/ui/src/components/chat/ChatInput.tsx index 879d0c35..efe9f79a 100644 --- a/packages/ui/src/components/chat/ChatInput.tsx +++ b/packages/ui/src/components/chat/ChatInput.tsx @@ -1749,6 +1749,10 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo queuedOnly?: boolean; queuedMessageId?: string; delivery?: 'steer'; + /** Submit this text instead of the composer input. Used by preset + starter chips: on mobile the collapsed pill has no mounted textarea, + so the DOM-first input snapshot would read empty content. */ + presetText?: string; }; const handleSubmitRef = React.useRef<(options?: SubmitOptions) => Promise>(async () => {}); @@ -1822,7 +1826,12 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo const queuedOnly = options?.queuedOnly ?? false; const queuedMessageId = options?.queuedMessageId; const delivery = options?.delivery === 'steer' && sessionPhase !== 'idle' ? 'steer' : undefined; - const inputSnapshot = getCurrentInputSnapshot(); + const inputSnapshot = options?.presetText != null + ? { + message: options.presetText, + hasContent: options.presetText.trim().length > 0 || sendableAttachedFiles.length > 0 || hasDrafts, + } + : getCurrentInputSnapshot(); const queuedMessagesToSend = queuedMessageId ? queuedMessages.filter((message) => message.id === queuedMessageId) : queuedMessages; @@ -2360,12 +2369,10 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo // getCurrentInputSnapshot reads textareaRef.current.value first, so setting it // synchronously lets handleSubmit pick up the preset text in the same tick. const submitPresetPrompt = React.useCallback((text: string) => { - const textarea = textareaRef.current; - if (textarea) { - textarea.value = text; - } - setMessage(text); - void handleSubmitRef.current(); + // The text goes straight into the submit (see SubmitOptions.presetText) + // instead of through the composer input — the collapsed mobile pill has + // no mounted textarea to stage it in. + void handleSubmitRef.current({ presetText: text }); }, []); // Dictation: insert the transcript inline; optionally submit immediately.