diff --git a/.plans/debug_2026-05-18_half_question_textarea_lock.md b/.plans/debug_2026-05-18_half_question_textarea_lock.md
new file mode 100644
index 0000000..fba42f1
--- /dev/null
+++ b/.plans/debug_2026-05-18_half_question_textarea_lock.md
@@ -0,0 +1,133 @@
+# 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:
+
+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: ← shared isLoading
+
+frontend/src/components/QueryInput.tsx
+ line 52: isDisabled = isLoading || displayValue.trim() === ''
+ line 66: ← THIS locks the textarea
+ line 81:
+ line 89:
+```
+
+---
+
+## 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:
+```tsx
+disabled={isLoading}
+```
+To:
+```tsx
+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
diff --git a/frontend/src/components/QueryInput.tsx b/frontend/src/components/QueryInput.tsx
index c8d770c..ccc23df 100644
--- a/frontend/src/components/QueryInput.tsx
+++ b/frontend/src/components/QueryInput.tsx
@@ -52,7 +52,7 @@ export const QueryInput: React.FC = ({ onSubmit, onHalfQuestion
const isDisabled = isLoading || displayValue.trim() === ''
const textareaClassName = [
- 'w-full rounded border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed',
+ 'w-full rounded border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent',
showPartialStyle ? 'text-gray-400 italic' : '',
].filter(Boolean).join(' ')
@@ -63,7 +63,6 @@ export const QueryInput: React.FC = ({ onSubmit, onHalfQuestion
onChange={handleChange}
onKeyDown={handleKeyDown}
placeholder="Ask a question about your documents..."
- disabled={isLoading}
rows={3}
className={textareaClassName}
/>
diff --git a/frontend/src/test/components/QueryInput.test.tsx b/frontend/src/test/components/QueryInput.test.tsx
index 440e0f6..fbc1f22 100644
--- a/frontend/src/test/components/QueryInput.test.tsx
+++ b/frontend/src/test/components/QueryInput.test.tsx
@@ -43,10 +43,10 @@ describe('QueryInput', () => {
expect(button).toBeDisabled()
})
- it('textarea is disabled when isLoading is true', () => {
+ it('textarea is NOT disabled when isLoading is true (remains editable)', () => {
render()
const textarea = screen.getByPlaceholderText('Ask a question about your documents...')
- expect(textarea).toBeDisabled()
+ expect(textarea).toBeEnabled()
})
it('button is disabled when isLoading is true even with text', () => {