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.
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: 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.
| 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 |
User enables "Show Thinking" toggle
→ 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
Add the new field to AITab and the new log source type.
- 1.1 In
src/renderer/types/index.ts, addshowThinking?: booleanto theAITabinterface (around line 289, afterscrollTop) - 1.2 In
src/renderer/types/index.ts, updateLogEntry.sourcetype (line 31) to include'thinking':source: 'stdout' | 'stderr' | 'system' | 'user' | 'ai' | 'thinking';
- 1.3 In
src/renderer/App.tsx, update thehandleToggleTabShowThinkinghandler pattern (similar to existinghandleToggleTabReadOnlyMode):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 ) }; })); }, []);
Add the "Show Thinking" toggle to InputArea, matching the existing History and Read-Only button patterns.
- 2.1 In
src/renderer/components/InputArea.tsx, importBrainicon from lucide-react (line ~2) - 2.2 Add props to
InputAreaPropsinterface (around line 74):tabShowThinking?: boolean; onToggleTabShowThinking?: () => void;
- 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):{/* 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 reasoning in real-time" > <Brain className="w-3 h-3" /> <span>Thinking</span> </button> )}
- 2.4 In
App.tsx, passtabShowThinkingandonToggleTabShowThinkingprops to InputArea (find the InputArea component usage and add these props)
Modify the main process to conditionally emit assistant message chunks.
- 3.1 In
src/main/process-manager.ts, addstreamAssistant?: booleanto theManagedProcessinterface (around line 20) - 3.2 In
src/main/process-manager.ts, modify the stream-json handling (lines 317-327) to conditionally emit assistant messages:// 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.3 Add the
'assistant-chunk'event type to the ProcessManager EventEmitter (check existing event patterns) - 3.4 In
src/main/preload.ts, expose a newonAssistantChunkcallback in the process API (similar toonData) - 3.5 In
src/main/index.ts, forward thestreamAssistantflag from spawn options to ProcessManager
Render thinking chunks in TerminalOutput with distinct styling.
- 4.1 In
src/renderer/App.tsx, add anonAssistantChunklistener in the useEffect that registers IPC listeners (similar to the existingonDatapattern):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; // Append to existing thinking entry or create new one const lastLog = activeTab.logs[activeTab.logs.length - 1]; if (lastLog?.source === 'thinking') { return { ...s, aiTabs: s.aiTabs.map(tab => tab.id === s.activeTabId ? { ...tab, logs: [...tab.logs.slice(0, -1), { ...lastLog, text: lastLog.text + content }] } : tab ) }; } else { 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, newLog] } : tab ) }; } })); });
- 4.2 In
src/renderer/components/TerminalOutput.tsx, add handling forlog.source === 'thinking'in the LogItem rendering (around line 280). Style with left border and dim italic text:{log.source === 'thinking' && ( <div 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`, opacity: 0.8 }} > <span className="text-xs mr-2 not-italic" style={{ color: theme.colors.info }}> thinking... </span> {log.text} </div> )}
- 4.3 When
resultmessage 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.
Handle edge cases and improve the experience.
- 5.1 Clear thinking block if process is interrupted (Ctrl+C / stop button)
- 5.2 Add keyboard shortcut
Cmd+Shift+Tfor toggling thinking (add tosrc/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
showThinkingstate when saving/loading sessions (add to session serialization)
| 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) |
From Claude Code's --output-format stream-json, assistant messages look like:
{
"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.
assistant messages may also contain tool use blocks. For simplicity, start by showing only text content. Tool visibility can be a future enhancement.
- 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
- Toggle appears next to History and Read-Only buttons with blue highlight
- Enabling shows real-time streaming with distinct visual styling
- Performance remains smooth with rapid chunk updates
- Toggle state persists per-tab
- Clean transition from thinking to final result