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.

Feature: Show Thinking Toggle

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: 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.

Key Files

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

Data Flow

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

Phase 1: Type Definitions & State

Add the new field to AITab and the new log source type.

Tasks

  • 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':
    source: 'stdout' | 'stderr' | 'system' | 'user' | 'ai' | 'thinking';
  • 1.3 In src/renderer/App.tsx, update the handleToggleTabShowThinking handler pattern (similar to existing handleToggleTabReadOnlyMode):
    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
          )
        };
      }));
    }, []);

Phase 2: UI Toggle Button

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

Tasks

  • 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):
    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, pass tabShowThinking and onToggleTabShowThinking props to InputArea (find the InputArea component usage and add these props)

Phase 3: Process Manager Streaming

Modify the main process to conditionally emit assistant message chunks.

Tasks

  • 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:
    // 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 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):
    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 for log.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 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 5: Polish & Edge Cases

Handle edge cases and improve the experience.

Tasks

  • 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)

Test Cases

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-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.

Tool Use in Thinking

assistant messages may also contain tool use blocks. For simplicity, start by showing only text content. Tool visibility can be a future enhancement.

Performance

  • 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. 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment