From 2fb8cfa2c2f11b711d1d2588775c25b5bc00af23 Mon Sep 17 00:00:00 2001 From: Om Gupta Date: Mon, 27 Apr 2026 19:27:04 +0530 Subject: [PATCH] fix(ask-user): commit answers only on explicit submit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generic AskUserInline previously auto-submitted as soon as a custom input had any non-empty trimmed value, treating focus/typing as intent. That made it easy to skip past a question by tabbing into the field, and the "Got it — Continuing…" footer was unreachable in practice. Now answers commit only when the user clicks an option, presses Enter, or hits the arrow button. Removes the showCustom toggle (no longer needed) and the dead done-state footer. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/components/chat/AskUserInline.tsx | 92 +++++++------------ 1 file changed, 31 insertions(+), 61 deletions(-) diff --git a/packages/desktop/src/components/chat/AskUserInline.tsx b/packages/desktop/src/components/chat/AskUserInline.tsx index f7c13403..d5a8f0da 100644 --- a/packages/desktop/src/components/chat/AskUserInline.tsx +++ b/packages/desktop/src/components/chat/AskUserInline.tsx @@ -385,7 +385,6 @@ function PublishConfirmCard({ export function AskUserInline({ questions, onSubmit }: Props) { const [answers, setAnswers] = useState>({}) const [customInputs, setCustomInputs] = useState>({}) - const [showCustom, setShowCustom] = useState>({}) // ── Specialized cards for agent operations ── if (questions.length === 1) { @@ -435,40 +434,31 @@ export function AskUserInline({ questions, onSubmit }: Props) { } // ── Generic rendering ── + // A question is "answered" only when committed to `answers`. Typing into the + // custom input is draft state — it does not advance the flow until the user + // explicitly submits via Enter or the arrow button. - const handleCustom = (question: string) => { - setShowCustom((prev) => ({ ...prev, [question]: true })) - setAnswers((prev) => ({ ...prev, [question]: '' })) - } + const answeredCount = questions.filter((q) => answers[q.question]).length - // Auto-submit when all questions have answers (and there are answers) - const checkAutoSubmit = ( - newAnswers: Record, - newCustom: Record, - ) => { - const done = questions.every((q) => newAnswers[q.question] || newCustom[q.question]?.trim()) + const commitAnswer = (question: string, value: string) => { + const trimmed = value.trim() + if (!trimmed) return + const newAnswers = { ...answers, [question]: trimmed } + setAnswers(newAnswers) + const done = questions.every((q) => newAnswers[q.question]) if (done) { - const final: Record = {} - for (const q of questions) { - final[q.question] = newCustom[q.question]?.trim() || newAnswers[q.question] || '' - } - // Small delay so user sees their selection - setTimeout(() => onSubmit(final), 300) + onSubmit(newAnswers) } } - const handleSelectAndCheck = (question: string, label: string) => { - const newAnswers = { ...answers, [question]: label } - const newCustom = { ...customInputs, [question]: '' } - setAnswers(newAnswers) - setCustomInputs(newCustom) - setShowCustom((prev) => ({ ...prev, [question]: false })) - checkAutoSubmit(newAnswers, newCustom) + const handleSelect = (question: string, label: string) => { + setCustomInputs((prev) => ({ ...prev, [question]: '' })) + commitAnswer(question, label) } - const answeredCount = questions.filter( - (q) => answers[q.question] || customInputs[q.question]?.trim(), - ).length + const handleSubmitCustom = (question: string) => { + commitAnswer(question, customInputs[question] || '') + } return (
@@ -499,24 +489,18 @@ export function AskUserInline({ questions, onSubmit }: Props) {
{(() => { - // Render only the first unanswered question — questions flow - // one at a time. Answered ones advance the progress dots in - // the header, and once all are done the ix__done block below - // fires the auto-submit. - const currentIndex = questions.findIndex( - (q) => !(answers[q.question] || customInputs[q.question]?.trim()), - ) + const currentIndex = questions.findIndex((q) => !answers[q.question]) if (currentIndex === -1) return null const q = questions[currentIndex] - const qi = currentIndex const options = (q.options ?? []).map(normalizeOption) - const isCustom = showCustom[q.question] + const customValue = customInputs[q.question] || '' + const customValid = customValue.trim().length > 0 return (
-
Q{qi + 1}
+
Q{currentIndex + 1}
{q.question}
@@ -526,7 +510,7 @@ export function AskUserInline({ questions, onSubmit }: Props) { key={opt.label} type="button" className="ix__opt" - onClick={() => handleSelectAndCheck(q.question, opt.label)} + onClick={() => handleSelect(q.question, opt.label)} > {i + 1} {opt.label} @@ -538,30 +522,30 @@ export function AskUserInline({ questions, onSubmit }: Props) { {q.allowFreeText !== false && (
- {isCustom ? 'Your answer' : 'Or write your own answer'} + {options.length > 0 ? 'Or write your own answer' : 'Your answer'}
handleCustom(q.question)} + value={customValue} onChange={(e) => { setCustomInputs((prev) => ({ ...prev, [q.question]: e.target.value })) }} onKeyDown={(e) => { - if (e.key === 'Enter' && customInputs[q.question]?.trim()) { - checkAutoSubmit(answers, customInputs) + if (e.key === 'Enter' && customValid) { + e.preventDefault() + handleSubmitCustom(q.question) } }} />
- )}
)