Skip to content

Instantly share code, notes, and snippets.

@pedramamini
Last active December 14, 2025 20:15
Show Gist options
  • Select an option

  • Save pedramamini/c505cb7498b652a4d980101ccc78e0d0 to your computer and use it in GitHub Desktop.

Select an option

Save pedramamini/c505cb7498b652a4d980101ccc78e0d0 to your computer and use it in GitHub Desktop.

Revisions

  1. pedramamini revised this gist Dec 14, 2025. 1 changed file with 132 additions and 220 deletions.
    352 changes: 132 additions & 220 deletions Feature-Show-Thinking.md
    Original file line number Diff line number Diff line change
    @@ -1,96 +1,81 @@
    # Feature: Show Thinking Toggle

    Stream Claude's thinking process in real-time instead of waiting for the final synopsis. A per-tab toggle button in the AI input area (alongside History and Read-Only) that enables live streaming of assistant messages.
    A simple toggle button in the AI terminal (bottom right of chat, alongside History and Read-Only) that lets users see Claude's thinking process as it streams, instead of waiting for the final result.

    ## Overview

    **Current Behavior**: When sending messages to AI agents, Maestro waits for the complete `type: 'result'` message and skips `type: 'assistant'` streaming chunks. Users see only the final response.
    **Current Behavior**: Maestro waits for the complete `type: 'result'` message from Claude Code's stream-json output. The `type: 'assistant'` streaming chunks are explicitly skipped (see `process-manager.ts:319-327`). Users see only the final polished response.

    **New Behavior**: With "Show Thinking" enabled, users see the agent's response as it streams in real-time, providing visibility into the reasoning process.
    **New Behavior**: When "Show Thinking" is enabled, users see the agent's response as it streams in real-time, giving visibility into reasoning before the final result arrives.

    ## Architecture Changes
    ## Key Files

    ### Key Files to Modify
    | File | Role |
    |------|------|
    | `src/main/process-manager.ts` | Emits streaming data (currently skips `assistant` messages at line 327) |
    | `src/renderer/components/InputArea.tsx` | Toggle buttons location (History, Read-Only are at lines 661-700) |
    | `src/renderer/components/TerminalOutput.tsx` | Log rendering (handles `LogEntry.source` types) |
    | `src/renderer/types/index.ts` | `AITab` interface (line 272), `LogEntry` interface (line 28) |
    | `src/renderer/App.tsx` | State management and IPC handler registration |
    | `src/main/preload.ts` | IPC bridge for new events |

    | File | Changes |
    |------|---------|
    | `src/main/process-manager.ts` | Emit `assistant` messages when streaming enabled |
    | `src/main/preload.ts` | Expose streaming preference per-session |
    | `src/renderer/components/InputArea.tsx` | Add toggle button UI |
    | `src/renderer/App.tsx` | Per-tab state management, pass to process |
    | `src/renderer/types/index.ts` | Add `showThinking` to AITab interface |
    | `src/main/index.ts` | Pass streaming flag through spawn options |

    ### Data Flow
    ## Data Flow

    ```
    User enables "Show Thinking" toggle
    → Tab state updated: aiTab.showThinking = true
    Next message spawn includes streaming flag
    → ProcessManager emits 'assistant' chunks as they arrive
    → App.tsx onData handler appends to tab logs with 'thinking' type
    UI renders thinking content with distinct styling
    → Final 'result' message replaces/consolidates thinking chunks
    → Tab state: aiTab.showThinking = true
    Main process checks flag before spawning
    → ProcessManager emits 'assistant-chunk' events (new event type)
    → App.tsx handler appends to tab logs with source: 'thinking'
    TerminalOutput renders thinking with distinct styling
    → Final 'result' replaces/consolidates thinking display
    ```

    ---

    ## Phase 1: Core Infrastructure
    ## Phase 1: Type Definitions & State

    Add the streaming capability to the process layer without UI yet.
    Add the new field to AITab and the new log source type.

    ### Tasks

    - [ ] **1.1** Add `showThinking?: boolean` field to `AITab` interface in `src/renderer/types/index.ts`
    - [ ] **1.2** Add `streamAssistant?: boolean` to spawn options interface in `src/main/process-manager.ts`
    - [ ] **1.3** Modify `process-manager.ts` lines 317-326 to conditionally emit `assistant` messages:
    - [ ] **1.1** In `src/renderer/types/index.ts`, add `showThinking?: boolean` to the `AITab` interface (around line 289, after `scrollTop`)
    - [ ] **1.2** In `src/renderer/types/index.ts`, update `LogEntry.source` type (line 31) to include `'thinking'`:
    ```typescript
    // Existing: Skip assistant messages
    // if (msg.type === 'assistant') { /* skipped */ }

    // New: Emit assistant messages if streaming enabled
    if (msg.type === 'assistant' && msg.message?.content && managedProcess.streamAssistant) {
    // Extract text content from assistant message
    const textContent = msg.message.content
    .filter((c: any) => c.type === 'text')
    .map((c: any) => c.text)
    .join('');
    if (textContent) {
    this.emit('thinking', sessionId, textContent);
    }
    }
    source: 'stdout' | 'stderr' | 'system' | 'user' | 'ai' | 'thinking';
    ```
    - [ ] **1.3** In `src/renderer/App.tsx`, update the `handleToggleTabShowThinking` handler pattern (similar to existing `handleToggleTabReadOnlyMode`):
    ```typescript
    const handleToggleTabShowThinking = useCallback((sessionId: string, tabId: string) => {
    setSessions(prev => prev.map(s => {
    if (s.id !== sessionId) return s;
    return {
    ...s,
    aiTabs: s.aiTabs.map(tab =>
    tab.id === tabId
    ? { ...tab, showThinking: !tab.showThinking }
    : tab
    )
    };
    }));
    }, []);
    ```
    - [ ] **1.4** Add new event type `'thinking'` to ProcessManager event emitter
    - [ ] **1.5** Update `preload.ts` to expose `onThinking` callback alongside `onData`
    - [ ] **1.6** Pass `streamAssistant` flag from spawn call in `App.tsx` to `process.spawn()`
    - [ ] **1.7** Update IPC handler in `index.ts` to forward `streamAssistant` option

    ### Test Cases - Phase 1

    | Test | Expected Result |
    |------|-----------------|
    | Spawn process with `streamAssistant: false` | No `thinking` events emitted |
    | Spawn process with `streamAssistant: true` | `thinking` events emitted for each `assistant` chunk |
    | `thinking` event content | Contains only text, no JSON structure |
    | `result` event still fires | Final complete response still emitted |
    | Multiple `assistant` chunks | Each chunk emits separate `thinking` event |

    ---

    ## Phase 2: UI Toggle Button

    Add the "Show Thinking" toggle to InputArea, matching existing patterns.
    Add the "Show Thinking" toggle to InputArea, matching the existing History and Read-Only button patterns.

    ### Tasks

    - [ ] **2.1** Import `Brain` icon from lucide-react in `InputArea.tsx` (or use `Sparkles`/`MessageSquareText`)
    - [ ] **2.2** Add props to `InputAreaProps` interface:
    - [ ] **2.1** In `src/renderer/components/InputArea.tsx`, import `Brain` icon from lucide-react (line ~2)
    - [ ] **2.2** Add props to `InputAreaProps` interface (around line 74):
    ```typescript
    // Show Thinking toggle (per-tab)
    tabShowThinking?: boolean;
    onToggleTabShowThinking?: () => void;
    ```
    - [ ] **2.3** Add toggle button between History and Read-Only buttons (line ~679):
    - [ ] **2.3** Add toggle button after the Read-Only button (around line 700), using `theme.colors.info` (blue) for the active state to differentiate from History (accent) and Read-Only (warning):
    ```tsx
    {/* Show Thinking toggle - AI mode only */}
    {session.inputMode === 'ai' && onToggleTabShowThinking && (
    @@ -104,194 +89,149 @@ Add the "Show Thinking" toggle to InputArea, matching existing patterns.
    color: tabShowThinking ? theme.colors.info : theme.colors.textDim,
    border: tabShowThinking ? `1px solid ${theme.colors.info}50` : '1px solid transparent'
    }}
    title="Show Thinking - Stream AI responses in real-time"
    title="Show Thinking - Stream AI reasoning in real-time"
    >
    <Brain className="w-3 h-3" />
    <span>Thinking</span>
    </button>
    )}
    ```
    - [ ] **2.4** Use `theme.colors.info` (blue) for active state to differentiate from History (accent) and Read-Only (warning/yellow)
    - [ ] **2.5** Update `App.tsx` to manage per-tab `showThinking` state
    - [ ] **2.6** Add `onToggleTabShowThinking` handler in `App.tsx`:
    ```typescript
    const handleToggleTabShowThinking = useCallback((sessionId: string, tabId: string) => {
    setSessions(prev => prev.map(s => {
    if (s.id !== sessionId) return s;
    return {
    ...s,
    aiTabs: s.aiTabs.map(tab =>
    tab.id === tabId
    ? { ...tab, showThinking: !tab.showThinking }
    : tab
    )
    };
    }));
    }, []);
    ```
    - [ ] **2.7** Pass `tabShowThinking` and `onToggleTabShowThinking` props to InputArea

    ### Test Cases - Phase 2

    | Test | Expected Result |
    |------|-----------------|
    | Toggle visible in AI mode | Button appears between History and Read-Only |
    | Toggle hidden in terminal mode | Button not rendered |
    | Click toggle when off | Turns on with blue highlight |
    | Click toggle when on | Turns off, becomes dim |
    | Hover on inactive toggle | Opacity increases |
    | Switch tabs | Toggle state preserved per-tab |
    | Tooltip on hover | Shows "Show Thinking - Stream AI responses in real-time" |
    - [ ] **2.4** In `App.tsx`, pass `tabShowThinking` and `onToggleTabShowThinking` props to InputArea (find the InputArea component usage and add these props)

    ---

    ## Phase 3: Thinking Display
    ## Phase 3: Process Manager Streaming

    Render thinking chunks in the AI terminal with distinct styling.
    Modify the main process to conditionally emit assistant message chunks.

    ### Tasks

    - [ ] **3.1** Add `'thinking'` to LogEntry type enum in `src/renderer/types/index.ts`:
    - [ ] **3.1** In `src/main/process-manager.ts`, add `streamAssistant?: boolean` to the `ManagedProcess` interface (around line 20)
    - [ ] **3.2** In `src/main/process-manager.ts`, modify the stream-json handling (lines 317-327) to conditionally emit assistant messages:
    ```typescript
    type: 'user' | 'assistant' | 'system' | 'error' | 'thinking';
    // Current code skips assistant messages (line 327):
    // Skip 'assistant' type - we prefer the complete 'result' over streaming chunks

    // New: Emit assistant messages if streaming is enabled
    if (msg.type === 'assistant' && msg.message?.content && managedProcess.streamAssistant) {
    const textContent = msg.message.content
    .filter((c: any) => c.type === 'text')
    .map((c: any) => c.text)
    .join('');
    if (textContent) {
    this.emit('assistant-chunk', sessionId, textContent);
    }
    }
    ```
    - [ ] **3.2** Update `onThinking` handler in `App.tsx` to append thinking entries to tab logs:
    - [ ] **3.3** Add the `'assistant-chunk'` event type to the ProcessManager EventEmitter (check existing event patterns)
    - [ ] **3.4** In `src/main/preload.ts`, expose a new `onAssistantChunk` callback in the process API (similar to `onData`)
    - [ ] **3.5** In `src/main/index.ts`, forward the `streamAssistant` flag from spawn options to ProcessManager

    ---

    ## Phase 4: Thinking Display in UI

    Render thinking chunks in TerminalOutput with distinct styling.

    ### Tasks

    - [ ] **4.1** In `src/renderer/App.tsx`, add an `onAssistantChunk` listener in the useEffect that registers IPC listeners (similar to the existing `onData` pattern):
    ```typescript
    const handleThinkingData = useCallback((sessionId: string, content: string) => {
    window.maestro.process.onAssistantChunk?.((sessionId: string, content: string) => {
    setSessions(prev => prev.map(s => {
    if (s.id !== sessionId) return s;
    const activeTab = s.aiTabs.find(t => t.id === s.activeTabId);
    if (!activeTab?.showThinking) return s; // Respect toggle state
    if (!activeTab?.showThinking) return s;

    // Append or update the last thinking entry
    // Append to existing thinking entry or create new one
    const lastLog = activeTab.logs[activeTab.logs.length - 1];
    if (lastLog?.type === 'thinking') {
    // Append to existing thinking block
    if (lastLog?.source === 'thinking') {
    return {
    ...s,
    aiTabs: s.aiTabs.map(tab =>
    tab.id === s.activeTabId
    ? { ...tab, logs: [...tab.logs.slice(0, -1), { ...lastLog, content: lastLog.content + content }] }
    ? { ...tab, logs: [...tab.logs.slice(0, -1), { ...lastLog, text: lastLog.text + content }] }
    : tab
    )
    };
    } else {
    // Start new thinking block
    const newLog: LogEntry = {
    id: `thinking-${Date.now()}`,
    timestamp: Date.now(),
    source: 'thinking',
    text: content
    };
    return {
    ...s,
    aiTabs: s.aiTabs.map(tab =>
    tab.id === s.activeTabId
    ? { ...tab, logs: [...tab.logs, { type: 'thinking', content, timestamp: Date.now() }] }
    ? { ...tab, logs: [...tab.logs, newLog] }
    : tab
    )
    };
    }
    }));
    }, []);
    });
    ```
    - [ ] **3.3** Register `onThinking` listener in App.tsx useEffect alongside `onData`
    - [ ] **3.4** Update log rendering in `AITerminal.tsx` (or wherever logs are rendered) to handle `thinking` type:
    - [ ] **4.2** In `src/renderer/components/TerminalOutput.tsx`, add handling for `log.source === 'thinking'` in the LogItem rendering (around line 280). Style with left border and dim italic text:
    ```tsx
    {log.type === 'thinking' && (
    {log.source === 'thinking' && (
    <div
    className="px-4 py-2 text-sm font-mono opacity-70 italic border-l-2"
    className="px-4 py-2 text-sm font-mono italic border-l-2"
    style={{
    color: theme.colors.textDim,
    borderColor: theme.colors.info,
    backgroundColor: `${theme.colors.info}08`
    backgroundColor: `${theme.colors.info}08`,
    opacity: 0.8
    }}
    >
    <span className="text-xs mr-2" style={{ color: theme.colors.info }}>thinking...</span>
    {log.content}
    <span className="text-xs mr-2 not-italic" style={{ color: theme.colors.info }}>
    thinking...
    </span>
    {log.text}
    </div>
    )}
    ```
    - [ ] **3.5** Add subtle animation/pulsing indicator while thinking is active
    - [ ] **3.6** When `result` message arrives, optionally collapse/replace thinking entries:
    - Option A: Keep thinking visible above final result (audit trail)
    - Option B: Replace thinking with final result (cleaner)
    - **Recommendation**: Option A with collapse toggle

    ### Test Cases - Phase 3

    | Test | Expected Result |
    |------|-----------------|
    | Thinking chunks appear | Streamed text visible with italic/dim styling |
    | Thinking has left border | Blue left border distinguishes from regular output |
    | Chunks concatenate | Multiple chunks merge into single thinking block |
    | Final result appears | Complete response shown after thinking |
    | Thinking toggle off mid-stream | No new thinking chunks appended |
    | Long thinking content | Scrolls properly, no layout breaking |
    | Rapid chunk updates | UI remains responsive, no flickering |
    - [ ] **4.3** When `result` message arrives, remove the thinking log entries and show final result (keeps UI clean). Alternatively, keep thinking visible but collapse it - decide based on user testing.

    ---

    ## Phase 4: State Persistence
    ## Phase 5: Polish & Edge Cases

    Persist the toggle state so it survives session restarts.
    Handle edge cases and improve the experience.

    ### Tasks

    - [ ] **4.1** Add `showThinking` to tab serialization in session save logic
    - [ ] **4.2** Load `showThinking` from persisted tab data on session restore
    - [ ] **4.3** Add global default setting `defaultShowThinking` in `useSettings.ts`:
    ```typescript
    const [defaultShowThinking, setDefaultShowThinkingState] = useState(false);

    const setDefaultShowThinking = (value: boolean) => {
    setDefaultShowThinkingState(value);
    window.maestro.settings.set('defaultShowThinking', value);
    };
    ```
    - [ ] **4.4** Apply global default when creating new tabs
    - [ ] **4.5** Add Settings modal toggle for global default (optional, can defer)

    ### Test Cases - Phase 4

    | Test | Expected Result |
    |------|-----------------|
    | Toggle on, restart app | Toggle state preserved |
    | New tab with default off | New tabs start with thinking off |
    | Change global default | New tabs use new default |
    | Existing tabs unchanged | Changing default doesn't affect existing tabs |
    - [ ] **5.1** Clear thinking block if process is interrupted (Ctrl+C / stop button)
    - [ ] **5.2** Add keyboard shortcut `Cmd+Shift+T` for toggling thinking (add to `src/renderer/constants/shortcuts.ts`)
    - [ ] **5.3** Add "Toggle Show Thinking" to command palette (Cmd+K menu)
    - [ ] **5.4** Debounce UI updates if chunks arrive very rapidly (e.g., 16ms throttle)
    - [ ] **5.5** Test with long thinking streams to ensure memory/performance is acceptable
    - [ ] **5.6** Ensure thinking content is excluded from synopsis generation (don't pollute History)
    - [ ] **5.7** Persist `showThinking` state when saving/loading sessions (add to session serialization)

    ---

    ## Phase 5: Polish & Edge Cases

    Handle edge cases and improve UX.
    ## Test Cases

    ### Tasks

    - [ ] **5.1** Handle agent disconnection during thinking stream
    - [ ] **5.2** Clear thinking block if process is interrupted (Ctrl+C)
    - [ ] **5.3** Add keyboard shortcut for toggle (e.g., `Cmd+Shift+T` for Thinking)
    - [ ] **5.4** Update shortcuts documentation in `src/renderer/constants/shortcuts.ts`
    - [ ] **5.5** Add thinking toggle to command palette (Cmd+K)
    - [ ] **5.6** Consider rate limiting UI updates if chunks arrive very rapidly (debounce)
    - [ ] **5.7** Test with non-Claude agents (ensure graceful handling if no assistant messages)
    - [ ] **5.8** Add "Thinking" label to status indicator while streaming
    - [ ] **5.9** Ensure thinking content is excluded from synopsis generation
    - [ ] **5.10** Test memory usage with long thinking streams

    ### Test Cases - Phase 5

    | Test | Expected Result |
    |------|-----------------|
    | Interrupt during thinking | Thinking block ends cleanly, no orphaned state |
    | Very rapid chunks | UI updates smoothly, no lag |
    | Non-Claude agent | Toggle visible but no effect (no assistant messages) |
    | Keyboard shortcut | `Cmd+Shift+T` toggles thinking |
    | Command palette | "Toggle Show Thinking" appears in Cmd+K |
    | Memory with 10MB of thinking | App remains responsive |
    | Scenario | Expected |
    |----------|----------|
    | Toggle off (default) | No thinking shown, only final result |
    | Toggle on, send message | Thinking streams in real-time with italic/dim styling |
    | Toggle on, multiple chunks | Chunks concatenate into single thinking block |
    | Toggle off mid-stream | Stops appending new chunks |
    | Final result arrives | Thinking replaced with final response |
    | Interrupt with stop button | Thinking block cleared |
    | Switch tabs | Toggle state preserved per-tab |
    | Restart app | Toggle state persisted (if Phase 5.7 done) |

    ---

    ## Implementation Notes

    ### Stream-JSON Message Format

    From Claude Code's output, `assistant` messages look like:
    From Claude Code's `--output-format stream-json`, assistant messages look like:

    ```json
    {
    @@ -301,10 +241,7 @@ From Claude Code's output, `assistant` messages look like:
    "type": "message",
    "role": "assistant",
    "content": [
    {
    "type": "text",
    "text": "Let me think about this..."
    }
    { "type": "text", "text": "Let me think about this..." }
    ],
    "model": "claude-sonnet-4-20250514",
    "stop_reason": null
    @@ -315,47 +252,22 @@ From Claude Code's output, `assistant` messages look like:

    The `content` array may contain multiple items; extract and concatenate all `type: 'text'` items.

    ### Thinking vs Tool Use

    `assistant` messages may also contain tool use blocks:
    ```json
    {
    "type": "tool_use",
    "id": "toolu_xxx",
    "name": "Read",
    "input": { "file_path": "/some/path" }
    }
    ```
    ### Tool Use in Thinking

    **Decision**: Should tool use be shown in thinking stream?
    - **Option A**: Show only text content (simpler, less noise)
    - **Option B**: Show tool names too (more transparency)
    - **Recommendation**: Start with Option A, add tool names as enhancement
    `assistant` messages may also contain tool use blocks. For simplicity, start by showing only text content. Tool visibility can be a future enhancement.

    ### Performance Considerations
    ### Performance

    - Thinking streams can be verbose (10KB+ per message)
    - Concatenating to log entries is O(n) - consider ring buffer for very long streams
    - Virtual scrolling may be needed for extremely long thinking output
    - Consider collapsing old thinking blocks to reduce DOM nodes
    - Thinking streams can be verbose (10KB+ per response)
    - String concatenation for log entries is O(n) - acceptable for typical use
    - Virtual scrolling already exists in TerminalOutput for long output

    ---

    ## Success Criteria

    1. **Functional**: Toggle enables/disables real-time streaming per tab
    2. **Performant**: No noticeable lag with rapid streaming updates
    3. **Persistent**: Toggle state survives app restart
    4. **Consistent**: UI matches existing History/Read-Only toggle patterns
    5. **Graceful**: Works with all agents, degrades gracefully for non-streaming agents

    ---

    ## Future Enhancements (Out of Scope)

    - [ ] Collapsible thinking sections
    - [ ] Syntax highlighting in thinking output
    - [ ] "Thinking time" metrics display
    - [ ] Export thinking transcript
    - [ ] Filter thinking by category (text vs tool use)
    - [ ] Global "Show Thinking for all tabs" quick toggle
    1. Toggle appears next to History and Read-Only buttons with blue highlight
    2. Enabling shows real-time streaming with distinct visual styling
    3. Performance remains smooth with rapid chunk updates
    4. Toggle state persists per-tab
    5. Clean transition from thinking to final result
  2. pedramamini created this gist Dec 14, 2025.
    361 changes: 361 additions & 0 deletions Feature-Show-Thinking.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,361 @@
    # Feature: Show Thinking Toggle

    Stream Claude's thinking process in real-time instead of waiting for the final synopsis. A per-tab toggle button in the AI input area (alongside History and Read-Only) that enables live streaming of assistant messages.

    ## Overview

    **Current Behavior**: When sending messages to AI agents, Maestro waits for the complete `type: 'result'` message and skips `type: 'assistant'` streaming chunks. Users see only the final response.

    **New Behavior**: With "Show Thinking" enabled, users see the agent's response as it streams in real-time, providing visibility into the reasoning process.

    ## Architecture Changes

    ### Key Files to Modify

    | File | Changes |
    |------|---------|
    | `src/main/process-manager.ts` | Emit `assistant` messages when streaming enabled |
    | `src/main/preload.ts` | Expose streaming preference per-session |
    | `src/renderer/components/InputArea.tsx` | Add toggle button UI |
    | `src/renderer/App.tsx` | Per-tab state management, pass to process |
    | `src/renderer/types/index.ts` | Add `showThinking` to AITab interface |
    | `src/main/index.ts` | Pass streaming flag through spawn options |

    ### Data Flow

    ```
    User enables "Show Thinking" toggle
    → Tab state updated: aiTab.showThinking = true
    → Next message spawn includes streaming flag
    → ProcessManager emits 'assistant' chunks as they arrive
    → App.tsx onData handler appends to tab logs with 'thinking' type
    → UI renders thinking content with distinct styling
    → Final 'result' message replaces/consolidates thinking chunks
    ```

    ---

    ## Phase 1: Core Infrastructure

    Add the streaming capability to the process layer without UI yet.

    ### Tasks

    - [ ] **1.1** Add `showThinking?: boolean` field to `AITab` interface in `src/renderer/types/index.ts`
    - [ ] **1.2** Add `streamAssistant?: boolean` to spawn options interface in `src/main/process-manager.ts`
    - [ ] **1.3** Modify `process-manager.ts` lines 317-326 to conditionally emit `assistant` messages:
    ```typescript
    // Existing: Skip assistant messages
    // if (msg.type === 'assistant') { /* skipped */ }

    // New: Emit assistant messages if streaming enabled
    if (msg.type === 'assistant' && msg.message?.content && managedProcess.streamAssistant) {
    // Extract text content from assistant message
    const textContent = msg.message.content
    .filter((c: any) => c.type === 'text')
    .map((c: any) => c.text)
    .join('');
    if (textContent) {
    this.emit('thinking', sessionId, textContent);
    }
    }
    ```
    - [ ] **1.4** Add new event type `'thinking'` to ProcessManager event emitter
    - [ ] **1.5** Update `preload.ts` to expose `onThinking` callback alongside `onData`
    - [ ] **1.6** Pass `streamAssistant` flag from spawn call in `App.tsx` to `process.spawn()`
    - [ ] **1.7** Update IPC handler in `index.ts` to forward `streamAssistant` option

    ### Test Cases - Phase 1

    | Test | Expected Result |
    |------|-----------------|
    | Spawn process with `streamAssistant: false` | No `thinking` events emitted |
    | Spawn process with `streamAssistant: true` | `thinking` events emitted for each `assistant` chunk |
    | `thinking` event content | Contains only text, no JSON structure |
    | `result` event still fires | Final complete response still emitted |
    | Multiple `assistant` chunks | Each chunk emits separate `thinking` event |

    ---

    ## Phase 2: UI Toggle Button

    Add the "Show Thinking" toggle to InputArea, matching existing patterns.

    ### Tasks

    - [ ] **2.1** Import `Brain` icon from lucide-react in `InputArea.tsx` (or use `Sparkles`/`MessageSquareText`)
    - [ ] **2.2** Add props to `InputAreaProps` interface:
    ```typescript
    // Show Thinking toggle (per-tab)
    tabShowThinking?: boolean;
    onToggleTabShowThinking?: () => void;
    ```
    - [ ] **2.3** Add toggle button between History and Read-Only buttons (line ~679):
    ```tsx
    {/* Show Thinking toggle - AI mode only */}
    {session.inputMode === 'ai' && onToggleTabShowThinking && (
    <button
    onClick={onToggleTabShowThinking}
    className={`flex items-center gap-1.5 text-[10px] px-2 py-1 rounded-full cursor-pointer transition-all ${
    tabShowThinking ? '' : 'opacity-40 hover:opacity-70'
    }`}
    style={{
    backgroundColor: tabShowThinking ? `${theme.colors.info}25` : 'transparent',
    color: tabShowThinking ? theme.colors.info : theme.colors.textDim,
    border: tabShowThinking ? `1px solid ${theme.colors.info}50` : '1px solid transparent'
    }}
    title="Show Thinking - Stream AI responses in real-time"
    >
    <Brain className="w-3 h-3" />
    <span>Thinking</span>
    </button>
    )}
    ```
    - [ ] **2.4** Use `theme.colors.info` (blue) for active state to differentiate from History (accent) and Read-Only (warning/yellow)
    - [ ] **2.5** Update `App.tsx` to manage per-tab `showThinking` state
    - [ ] **2.6** Add `onToggleTabShowThinking` handler in `App.tsx`:
    ```typescript
    const handleToggleTabShowThinking = useCallback((sessionId: string, tabId: string) => {
    setSessions(prev => prev.map(s => {
    if (s.id !== sessionId) return s;
    return {
    ...s,
    aiTabs: s.aiTabs.map(tab =>
    tab.id === tabId
    ? { ...tab, showThinking: !tab.showThinking }
    : tab
    )
    };
    }));
    }, []);
    ```
    - [ ] **2.7** Pass `tabShowThinking` and `onToggleTabShowThinking` props to InputArea

    ### Test Cases - Phase 2

    | Test | Expected Result |
    |------|-----------------|
    | Toggle visible in AI mode | Button appears between History and Read-Only |
    | Toggle hidden in terminal mode | Button not rendered |
    | Click toggle when off | Turns on with blue highlight |
    | Click toggle when on | Turns off, becomes dim |
    | Hover on inactive toggle | Opacity increases |
    | Switch tabs | Toggle state preserved per-tab |
    | Tooltip on hover | Shows "Show Thinking - Stream AI responses in real-time" |

    ---

    ## Phase 3: Thinking Display

    Render thinking chunks in the AI terminal with distinct styling.

    ### Tasks

    - [ ] **3.1** Add `'thinking'` to LogEntry type enum in `src/renderer/types/index.ts`:
    ```typescript
    type: 'user' | 'assistant' | 'system' | 'error' | 'thinking';
    ```
    - [ ] **3.2** Update `onThinking` handler in `App.tsx` to append thinking entries to tab logs:
    ```typescript
    const handleThinkingData = useCallback((sessionId: string, content: string) => {
    setSessions(prev => prev.map(s => {
    if (s.id !== sessionId) return s;
    const activeTab = s.aiTabs.find(t => t.id === s.activeTabId);
    if (!activeTab?.showThinking) return s; // Respect toggle state

    // Append or update the last thinking entry
    const lastLog = activeTab.logs[activeTab.logs.length - 1];
    if (lastLog?.type === 'thinking') {
    // Append to existing thinking block
    return {
    ...s,
    aiTabs: s.aiTabs.map(tab =>
    tab.id === s.activeTabId
    ? { ...tab, logs: [...tab.logs.slice(0, -1), { ...lastLog, content: lastLog.content + content }] }
    : tab
    )
    };
    } else {
    // Start new thinking block
    return {
    ...s,
    aiTabs: s.aiTabs.map(tab =>
    tab.id === s.activeTabId
    ? { ...tab, logs: [...tab.logs, { type: 'thinking', content, timestamp: Date.now() }] }
    : tab
    )
    };
    }
    }));
    }, []);
    ```
    - [ ] **3.3** Register `onThinking` listener in App.tsx useEffect alongside `onData`
    - [ ] **3.4** Update log rendering in `AITerminal.tsx` (or wherever logs are rendered) to handle `thinking` type:
    ```tsx
    {log.type === 'thinking' && (
    <div
    className="px-4 py-2 text-sm font-mono opacity-70 italic border-l-2"
    style={{
    color: theme.colors.textDim,
    borderColor: theme.colors.info,
    backgroundColor: `${theme.colors.info}08`
    }}
    >
    <span className="text-xs mr-2" style={{ color: theme.colors.info }}>thinking...</span>
    {log.content}
    </div>
    )}
    ```
    - [ ] **3.5** Add subtle animation/pulsing indicator while thinking is active
    - [ ] **3.6** When `result` message arrives, optionally collapse/replace thinking entries:
    - Option A: Keep thinking visible above final result (audit trail)
    - Option B: Replace thinking with final result (cleaner)
    - **Recommendation**: Option A with collapse toggle

    ### Test Cases - Phase 3

    | Test | Expected Result |
    |------|-----------------|
    | Thinking chunks appear | Streamed text visible with italic/dim styling |
    | Thinking has left border | Blue left border distinguishes from regular output |
    | Chunks concatenate | Multiple chunks merge into single thinking block |
    | Final result appears | Complete response shown after thinking |
    | Thinking toggle off mid-stream | No new thinking chunks appended |
    | Long thinking content | Scrolls properly, no layout breaking |
    | Rapid chunk updates | UI remains responsive, no flickering |

    ---

    ## Phase 4: State Persistence

    Persist the toggle state so it survives session restarts.

    ### Tasks

    - [ ] **4.1** Add `showThinking` to tab serialization in session save logic
    - [ ] **4.2** Load `showThinking` from persisted tab data on session restore
    - [ ] **4.3** Add global default setting `defaultShowThinking` in `useSettings.ts`:
    ```typescript
    const [defaultShowThinking, setDefaultShowThinkingState] = useState(false);

    const setDefaultShowThinking = (value: boolean) => {
    setDefaultShowThinkingState(value);
    window.maestro.settings.set('defaultShowThinking', value);
    };
    ```
    - [ ] **4.4** Apply global default when creating new tabs
    - [ ] **4.5** Add Settings modal toggle for global default (optional, can defer)

    ### Test Cases - Phase 4

    | Test | Expected Result |
    |------|-----------------|
    | Toggle on, restart app | Toggle state preserved |
    | New tab with default off | New tabs start with thinking off |
    | Change global default | New tabs use new default |
    | Existing tabs unchanged | Changing default doesn't affect existing tabs |

    ---

    ## Phase 5: Polish & Edge Cases

    Handle edge cases and improve UX.

    ### Tasks

    - [ ] **5.1** Handle agent disconnection during thinking stream
    - [ ] **5.2** Clear thinking block if process is interrupted (Ctrl+C)
    - [ ] **5.3** Add keyboard shortcut for toggle (e.g., `Cmd+Shift+T` for Thinking)
    - [ ] **5.4** Update shortcuts documentation in `src/renderer/constants/shortcuts.ts`
    - [ ] **5.5** Add thinking toggle to command palette (Cmd+K)
    - [ ] **5.6** Consider rate limiting UI updates if chunks arrive very rapidly (debounce)
    - [ ] **5.7** Test with non-Claude agents (ensure graceful handling if no assistant messages)
    - [ ] **5.8** Add "Thinking" label to status indicator while streaming
    - [ ] **5.9** Ensure thinking content is excluded from synopsis generation
    - [ ] **5.10** Test memory usage with long thinking streams

    ### Test Cases - Phase 5

    | Test | Expected Result |
    |------|-----------------|
    | Interrupt during thinking | Thinking block ends cleanly, no orphaned state |
    | Very rapid chunks | UI updates smoothly, no lag |
    | Non-Claude agent | Toggle visible but no effect (no assistant messages) |
    | Keyboard shortcut | `Cmd+Shift+T` toggles thinking |
    | Command palette | "Toggle Show Thinking" appears in Cmd+K |
    | Memory with 10MB of thinking | App remains responsive |

    ---

    ## Implementation Notes

    ### Stream-JSON Message Format

    From Claude Code's output, `assistant` messages look like:

    ```json
    {
    "type": "assistant",
    "message": {
    "id": "msg_xxx",
    "type": "message",
    "role": "assistant",
    "content": [
    {
    "type": "text",
    "text": "Let me think about this..."
    }
    ],
    "model": "claude-sonnet-4-20250514",
    "stop_reason": null
    },
    "session_id": "xxx"
    }
    ```

    The `content` array may contain multiple items; extract and concatenate all `type: 'text'` items.

    ### Thinking vs Tool Use

    `assistant` messages may also contain tool use blocks:
    ```json
    {
    "type": "tool_use",
    "id": "toolu_xxx",
    "name": "Read",
    "input": { "file_path": "/some/path" }
    }
    ```

    **Decision**: Should tool use be shown in thinking stream?
    - **Option A**: Show only text content (simpler, less noise)
    - **Option B**: Show tool names too (more transparency)
    - **Recommendation**: Start with Option A, add tool names as enhancement

    ### Performance Considerations

    - Thinking streams can be verbose (10KB+ per message)
    - Concatenating to log entries is O(n) - consider ring buffer for very long streams
    - Virtual scrolling may be needed for extremely long thinking output
    - Consider collapsing old thinking blocks to reduce DOM nodes

    ---

    ## Success Criteria

    1. **Functional**: Toggle enables/disables real-time streaming per tab
    2. **Performant**: No noticeable lag with rapid streaming updates
    3. **Persistent**: Toggle state survives app restart
    4. **Consistent**: UI matches existing History/Read-Only toggle patterns
    5. **Graceful**: Works with all agents, degrades gracefully for non-streaming agents

    ---

    ## Future Enhancements (Out of Scope)

    - [ ] Collapsible thinking sections
    - [ ] Syntax highlighting in thinking output
    - [ ] "Thinking time" metrics display
    - [ ] Export thinking transcript
    - [ ] Filter thinking by category (text vs tool use)
    - [ ] Global "Show Thinking for all tabs" quick toggle