5.8 KiB
Debug Plan: Half Question Button Locks QueryBox
Date: 2026-05-18 Symptom: After clicking "Half Question", the textarea appears locked (grayed out, cursor-not-allowed), raising concern that incoming ASR text chunks won't accumulate.
Root Cause Analysis
The Disable Chain
User clicks "Half Question"
→ QueryInput.onClick: onHalfQuestion(trimmed)
→ LTTPage.handleHalfQuestion: queryStream.decomposeOnly({ question })
→ queries.tsx decomposeOnly: setState({ phase: 'decomposing' }) [IMMEDIATE]
→ LTTPage: isLoading = true (phase !== 'idle' && !== 'completed' && !== 'error')
→ QueryInput.textarea: disabled={isLoading} = true ← TEXTAREA LOCKS
→ QueryInput.buttons: disabled={isDisabled} = true ← BUTTONS LOCK
What ACTUALLY Happens vs What User Perceives
| Concern | Reality |
|---|---|
| Text accumulation stops? | No. React controlled value={displayValue} updates even when disabled. ASR text continues to display. Playwright confirmed: 250→421 chars during Half Question processing. |
| Buttons permanently locked? | No. When SSE completed event arrives, phase→completed, isLoading→false, all re-enabled. |
| Textarea editable during wait? | No. This IS the issue. Textarea is disabled={isLoading}, so user cannot edit/select text during the 5-30s LLM decomposition call. |
The Real Bug
The textarea should NOT be disabled during half-question processing. The half-question feature is meant to be non-blocking — users should be able to:
- Continue seeing ASR text accumulate ✅ (works via controlled component)
- Edit the accumulating text ❌ (textarea disabled prevents typing/selection)
- Click "Final Submit" later ❌ (button disabled during API call)
- Click "Half Question" again ❌ (button disabled during API call)
But #2 is the primary concern. Users physically cannot interact with the textarea while the LLM decomposes the question — and they can't even see that text IS still accumulating because the grayed-out disabled style makes it look frozen.
Where Every Prop Wires
frontend/src/pages/LTTPage.tsx
line 73: handleHalfQuestion → queryStream.decomposeOnly({ question })
line 81: isLoading = phase !== 'idle' && !== 'completed' && !== 'error'
line 166: <QueryInput isLoading={isLoading} ... /> ← shared isLoading
frontend/src/components/QueryInput.tsx
line 52: isDisabled = isLoading || displayValue.trim() === ''
line 66: <textarea disabled={isLoading} ... /> ← THIS locks the textarea
line 81: <button "Half Question" disabled={isDisabled} />
line 89: <button "Final Submit" disabled={isDisabled} />
Root Cause Summary
Line 66 of QueryInput.tsx: disabled={isLoading}
The textarea shares isLoading with the buttons. During the API call, isLoading=true, which:
disabledattribute on textarea → gray background, no cursor, no user input- Buttons become disabled via
isDisabled(line 52)
The textarea does not need to be disabled during an API call — the buttons are sufficient to prevent duplicate submissions. The textarea should remain interactive so users can continue seeing and editing the accumulating ASR text.
Fix Plan
Step 1: Remove disabled={isLoading} from textarea (line 66)
Change:
disabled={isLoading}
To:
disabled={false} // or remove the disabled prop entirely
The textarea should never be disabled — isDisabled on the buttons already prevents submissions when text is empty.
Step 2: Keep buttons on isDisabled (lines 81, 89)
Buttons continue to use isDisabled = isLoading || displayValue.trim() === '' — they remain disabled during API calls AND when text is empty.
Step 3: (Optional) Remove disabled:bg-gray-100 disabled:cursor-not-allowed from textarea CSS
Line 55-57 of the textareaClassName includes disabled:bg-gray-100 disabled:cursor-not-allowed — if the textarea is no longer disabled, these are inert. Can be removed for cleanliness but not required.
Risk Assessment
| Risk | Severity | Mitigation |
|---|---|---|
| User types while LLM uses old text | Low | Half Question already sent with the text at click time. ASR accumulation doesn't invalidate previous half-question results. |
| Double-click Half Question | Low | isDisabled on the button prevents this (isLoading=true during call). |
| User edits text then clicks Final Submit | None | This is desired behavior. |
Test Update
File: frontend/src/test/test_phase2_QueryInput_integration.test.tsx
No test currently asserts that the textarea is disabled during isLoading. Only tests check button disabled state. If any test relies on textarea being disabled, update it to expect toBeEnabled().
Phase Sequence Timeline (decomposeOnly)
t=0ms setState({ phase: 'decomposing' }) → isLoading=true (textarea locked, buttons locked)
t=2s SSE event 'decomposed' → phase='retrieving' (still locked)
t=3s SSE event 'completed' → phase='completed' → isLoading=false (UNLOCKED)
The textarea is locked for ~3 seconds (LLM network round trip + decomposition time). This is the window where users see the grayed-out textarea and think text has stopped accumulating.
Verification Checklist
- Textarea never gets
disabledattribute, regardless ofisLoading - Textarea remains editable/selectable during API calls
- ASR partial text continues to display in textarea during API calls
- Half Question button disabled during API call (prevents double-click)
- Final Submit button disabled during API call
- Both buttons re-enable after API call completes
- Both buttons disabled when textarea is empty
pnpm testpasses- Playwright confirms: textarea interactive during ASR streaming + half-question processing