Global: AI Drawer chat history - #4035
Conversation
the entire drawer when typing
opened on a non-supported app
and ensured that prompt approval status is updated when a prompt is auto-applied
useLocalStorage hook
Code Review — ✅ No blockers · 🟡 4 warning(s) — see inline comments |
latestPromptZUIDs.values().next().value only ever picked one entry out of the set, so a log delta surfacing more than one new prompt ZUID at once silently skipped auto-applying the rest. Iterate the whole set and batch the resulting optimistic approval update into a single setResponses call.
Code Review — ✅ No blockers · 🟡 2 warning(s) — see inline comments |
Code Review — ✅ No blockers · 🟡 1 warning(s) — see inline comments |
Code Review — ✅ No blockers · 🟡 3 warning(s) — see inline comments |
Code Review — ✅ No blockers · 🟡 1 warning(s) — see inline comments |
Code Review — ✅ No blockers · 🟡 2 warning(s) — see inline comments |
| ? `Generate suggestions: ${normalizedPrompt}` | ||
| : "Generate suggestions for my content fields"; | ||
|
|
||
| isAwaitingLiveResponseRef.current = true; |
There was a problem hiding this comment.
🟡 handleGenerateSuggestions fires without roleZuid when roles haven't loaded, silently creating an orphaned session
handlePrompt (line 383) guards with if (!newPrompt?.trim() || !userRole?.ZUID) return; before calling geminiGenerate. handleGenerateSuggestions has no such guard: when !urlChatZUID && !userRole?.ZUID (roles still loading), the spread on line 459 emits no roleZuid, yet isAwaitingLiveResponseRef.current is already set to true and pendingPrompt is optimistically rendered. If the backend requires roleZuid to create a new session, the API call fails silently—the loading indicator never clears and the pending message stays stuck.
| isAwaitingLiveResponseRef.current = true; | |
| if (!urlChatZUID && !userRole?.ZUID) return; | |
| isAwaitingLiveResponseRef.current = true; |
| ); | ||
| }, [sessions, searchTerm]); | ||
|
|
||
| const handleSearch = useMemo( |
There was a problem hiding this comment.
🟡 Debounced function not cancelled on unmount — pending timer fires against detached state
debounce returns a function with an internal timer. useMemo with [] creates it once but never schedules cleanup. If ChatHistory unmounts while a keystroke is still in the 300 ms window (e.g., the user navigates away mid-typing), lodash fires the callback and calls setSearchTerm on the old, now-detached component instance. React 18 silences the warning but the stale state update can produce surprising results if the component remounts quickly.
| const handleSearch = useMemo( | |
| const handleSearch = useMemo( | |
| () => debounce((term: string) => setSearchTerm(term), 300), | |
| [] | |
| ); | |
| useEffect(() => () => handleSearch.cancel(), [handleSearch]); |
| {response.payload?.value?.startsWith("3-") ? ( | ||
| <GeneratedImage src={response.payload.value} /> | ||
| ) : ( | ||
| <AnimatedText |
There was a problem hiding this comment.
🟡 AnimatedText crashes when response.payload.value is undefined and animate is true
response.payload.value is typed as any (comes from server JSON). If the AI backend ever returns a response object whose payload lacks a value key (e.g. a new action type, a partial response, or a regression in the MCP API), text is undefined. AnimatedText's interval then accesses text.length and throws TypeError: Cannot read properties of undefined (reading 'length'), crashing the component tree.
The ternary on line 277 uses optional chaining (response.payload?.value?.startsWith) so it safely routes undefined into this branch rather than the image branch, making the crash reachable.
Fix: guard the text prop at the call site or inside AnimatedText.
| <AnimatedText | |
| <AnimatedText | |
| key={`${promptZUID}-${responseIndex}`} | |
| text={response.payload.value ?? ""} | |
| animate={shouldAnimate && !isInCodeApp} |
Code Review — ✅ No blockers · 🟡 3 warning(s) — see inline comments |
Resolves #4031
Resolves #4134
Summary
/clientAPI, with newgetChatSessions,getChatSessionLog, andupdatePromptApprovalStatusendpoints onmcpApi.Cannot read properties of undefined (reading 'map')) wheresetResponsesupdaters assumed a prompt's response array still existed after the active chat session was switched or cleared mid-flight.src/shell/views/Shell/AIDrawer.tsx(a single 779-line file) intosrc/shell/components/AIDrawer/, split by concern (index.tsx,ChatThread.tsx,ChatHistory.tsx,PromptComposer.tsx,AnimatedText.tsx,GeneratedImage.tsx).Dependency
Test plan
cypress/e2e/shell/ai-drawer.spec.jspasses end-to-end.Screenshots
Screen.Recording.2026-08-18.140538.mov