legco_ai_assistant/.plans/debug_2026-05-18_half_quest...

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, isLoadingfalse, 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:

  1. Continue seeing ASR text accumulate (works via controlled component)
  2. Edit the accumulating text (textarea disabled prevents typing/selection)
  3. Click "Final Submit" later (button disabled during API call)
  4. 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:

  • disabled attribute 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 disabled attribute, regardless of isLoading
  • 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 test passes
  • Playwright confirms: textarea interactive during ASR streaming + half-question processing