From 58b016ba99c8427e480efda450bde5eb68e414c8 Mon Sep 17 00:00:00 2001 From: Ramnique Singh <30795890+ramnique@users.noreply.github.com> Date: Thu, 2 Jul 2026 11:43:25 +0530 Subject: [PATCH] fix(x/renderer): surface permission/ask-human cards and load errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ChatSidebar assembles the active tab's view state from individual props, not from chatTabStates — and the four permission/ask-human maps were still fed from legacy standalone state that nothing populates under the sessions runtime. Pending permission requests (recorded correctly in the turn file) therefore never rendered, leaving the chat on the 'Thinking…' shimmer. - Feed pendingAskHumanRequests / allPermissionRequests / permissionResponses / autoPermissionDecisions from the sessions hook (legacy fallback until stage 7). - New optional isThinking prop on ChatSidebar: waiting on a permission/ask-human no longer shimmers (isProcessing still blocks the composer, per the state contract). - Session load failures are now visible: an error item renders in the chat instead of a silent blank, and the store logs load/reload failures to the console. Verified the full data path against the real on-disk session (the suspended list-downloads turn): conversation renders and the pending permission card map carries the request, under StrictMode. Co-Authored-By: Claude Fable 5 --- apps/x/apps/renderer/src/App.tsx | 18 +++++++++++++----- .../renderer/src/components/chat-sidebar.tsx | 7 ++++++- .../renderer/src/lib/session-chat/store.ts | 4 +++- 3 files changed, 22 insertions(+), 7 deletions(-) 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) } }