diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index 61a5a471..8b2a32f4 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -2728,6 +2728,13 @@ function App() { handlePromptSubmitRef.current?.({ text: `${name} connected successfully.`, files: [] }) }, []) + // A failed session load must be visible, not a blank chat. + const sessionLoadErrorItems = React.useMemo(() => ( + sessionChat.error + ? [{ id: 'session-load-error', kind: 'error', message: `Failed to load chat: ${sessionChat.error}`, timestamp: 0 }] + : [] + ), [sessionChat.error]) + // Active tab renders from the sessions hook; background tabs keep the // legacy cached view state until stage 7 retires it. const chatTabStatesForRender = React.useMemo(() => { @@ -6303,7 +6310,7 @@ function App() { }} onOpenChatHistory={() => void navigateToView({ type: 'chat-history' })} onOpenFullScreen={toggleRightPaneMaximize} - conversation={sessionChat.chatState?.conversation ?? conversation} + conversation={sessionChat.chatState?.conversation ?? (sessionLoadErrorItems.length > 0 ? sessionLoadErrorItems : conversation)} currentAssistantMessage={sessionChat.chatState?.currentAssistantMessage ?? currentAssistantMessage} chatTabStates={chatTabStatesForRender} viewportAnchors={chatViewportAnchorByTab} @@ -6338,10 +6345,11 @@ function App() { ? { title: activeCodeSession.session.title } : null } - pendingAskHumanRequests={pendingAskHumanRequests} - allPermissionRequests={allPermissionRequests} - permissionResponses={permissionResponses} - autoPermissionDecisions={autoPermissionDecisions} + pendingAskHumanRequests={sessionChat.chatState?.pendingAskHumanRequests ?? pendingAskHumanRequests} + allPermissionRequests={sessionChat.chatState?.allPermissionRequests ?? allPermissionRequests} + permissionResponses={sessionChat.chatState?.permissionResponses ?? permissionResponses} + autoPermissionDecisions={sessionChat.chatState?.autoPermissionDecisions ?? autoPermissionDecisions} + isThinking={sessionChat.chatState?.isThinking} onPermissionResponse={handlePermissionResponse} onAskHumanResponse={handleAskHumanResponse} isToolOpenForTab={isToolOpenForTab} diff --git a/apps/x/apps/renderer/src/components/chat-sidebar.tsx b/apps/x/apps/renderer/src/components/chat-sidebar.tsx index a9d6d9f7..976f9684 100644 --- a/apps/x/apps/renderer/src/components/chat-sidebar.tsx +++ b/apps/x/apps/renderer/src/components/chat-sidebar.tsx @@ -142,6 +142,10 @@ interface ChatSidebarProps { chatTabStates?: Record viewportAnchors?: Record isProcessing: boolean + // Actively working (sessions runtime). When provided, drives the shimmer + // instead of isProcessing so waiting on a permission/ask-human doesn't + // render a "Thinking…" under the card. + isThinking?: boolean isStopping?: boolean onStop?: () => void onSubmit: (message: PromptInputMessage, mentions?: FileMention[], attachments?: StagedAttachment[], searchEnabled?: boolean, codeMode?: 'claude' | 'codex', permissionMode?: PermissionMode) => void @@ -211,6 +215,7 @@ export function ChatSidebar({ chatTabStates = {}, viewportAnchors = {}, isProcessing, + isThinking, isStopping, onStop, onSubmit, @@ -754,7 +759,7 @@ export function ChatSidebar({ )} - {isActive && isProcessing && !tabState.currentAssistantMessage && ( + {isActive && (isThinking ?? isProcessing) && !tabState.currentAssistantMessage && ( Thinking... diff --git a/apps/x/apps/renderer/src/lib/session-chat/store.ts b/apps/x/apps/renderer/src/lib/session-chat/store.ts index 42408d4c..2bd0b4b4 100644 --- a/apps/x/apps/renderer/src/lib/session-chat/store.ts +++ b/apps/x/apps/renderer/src/lib/session-chat/store.ts @@ -115,6 +115,7 @@ export class SessionChatStore { this.emit() } catch (error) { if (generation !== this.generation) return + console.error('[session-chat] failed to load session', sessionId, error) this.loading = false this.error = error instanceof Error ? error.message : String(error) this.emit() @@ -164,8 +165,9 @@ export class SessionChatStore { } this.latestEvents = turn.events this.emit() - } catch { + } catch (error) { // The next snapshot-worthy event will retry. + console.error('[session-chat] failed to reload turn', turnId, error) } }