diff --git a/src/renderer/components/ChatPane.tsx b/src/renderer/components/ChatPane.tsx index 39f144e..1a5f477 100644 --- a/src/renderer/components/ChatPane.tsx +++ b/src/renderer/components/ChatPane.tsx @@ -143,6 +143,9 @@ export function ChatPane({ let lastUserMessageId: string | undefined; const messages = session.messages; const busy = session.status === 'running'; + // Track which runs have been attached to a turn-activity item so we + // can detect orphaned runs that need their own panel. + const consumedRunIds = new Set(); for (let i = 0; i < messages.length; i++) { const message = messages[i]; @@ -173,6 +176,7 @@ export function ChatPane({ if (pendingThinking.length > 0) { const run = lastUserMessageId ? runsByTrigger.get(lastUserMessageId) : undefined; + if (run) consumedRunIds.add(run.id); items.push({ type: 'turn-activity', thinkingMessages: pendingThinking, run, turnStartedAt: run?.startedAt }); pendingThinking = []; } @@ -184,9 +188,20 @@ export function ChatPane({ if (pendingThinking.length > 0) { const run = lastUserMessageId ? runsByTrigger.get(lastUserMessageId) : undefined; + if (run) consumedRunIds.add(run.id); items.push({ type: 'turn-activity', thinkingMessages: pendingThinking, run, turnStartedAt: run?.startedAt }); } + // If the session is busy but no turn-activity was emitted for the + // active run (thinking messages haven't been reclassified yet), inject + // one so the panel appears as soon as the run starts producing events. + if (lastUserMessageId) { + const activeRun = runsByTrigger.get(lastUserMessageId); + if (activeRun && !consumedRunIds.has(activeRun.id)) { + items.push({ type: 'turn-activity', thinkingMessages: [], run: activeRun, turnStartedAt: activeRun.startedAt }); + } + } + return items; }, [session.messages, session.runs, session.status]); diff --git a/src/renderer/components/chat/TurnActivityPanel.tsx b/src/renderer/components/chat/TurnActivityPanel.tsx index f26f3a4..4aaa5e6 100644 --- a/src/renderer/components/chat/TurnActivityPanel.tsx +++ b/src/renderer/components/chat/TurnActivityPanel.tsx @@ -273,17 +273,16 @@ export function TurnActivityPanel({ const [expanded, setExpanded] = useState(false); const wasActiveRef = useRef(isActive); - // Auto-expand when the turn is active and activity appears. + // Auto-expand when the turn is active (run exists or thinking arrives). // Auto-collapse once the turn finishes. useEffect(() => { - const hasActivity = thinkingMessages.length > 0 || (run?.events.length ?? 0) > 0; - if (isActive && hasActivity) { + if (isActive && (thinkingMessages.length > 0 || run)) { setExpanded(true); } else if (wasActiveRef.current && !isActive) { setExpanded(false); } wasActiveRef.current = isActive; - }, [isActive, thinkingMessages.length, run?.events.length]); + }, [isActive, thinkingMessages.length, run]); const toggle = useCallback(() => setExpanded((prev) => !prev), []); @@ -302,8 +301,8 @@ export function TurnActivityPanel({ [thinkingMessages, run?.events], ); - // Nothing to show yet - if (thinkingMessages.length === 0 && (!run || run.events.length === 0)) { + // Nothing to show — no thinking messages, no run, and not active + if (thinkingMessages.length === 0 && !run) { return null; }