fix(x/renderer): surface permission/ask-human cards and load errors

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 <noreply@anthropic.com>
This commit is contained in:
Ramnique Singh 2026-07-02 11:43:25 +05:30
parent ec5a534465
commit 58b016ba99
3 changed files with 22 additions and 7 deletions

View file

@ -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<ConversationItem[]>(() => (
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}

View file

@ -142,6 +142,10 @@ interface ChatSidebarProps {
chatTabStates?: Record<string, ChatTabViewState>
viewportAnchors?: Record<string, ChatViewportAnchorState>
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({
</Message>
)}
{isActive && isProcessing && !tabState.currentAssistantMessage && (
{isActive && (isThinking ?? isProcessing) && !tabState.currentAssistantMessage && (
<Message from="assistant">
<MessageContent>
<Shimmer duration={1}>Thinking...</Shimmer>

View file

@ -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)
}
}