mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-13 01:47:24 +00:00
fix(cursor-chat): stabilize first-run UX and clean cursor message rendering
Fix three Cursor chat regressions observed on first message runs: 1. Full-screen UI refresh/flicker after first response. 2. Internal wrapper tags rendered in user messages. 3. Duplicate assistant message on response finalization. Root causes - Project refresh from chat completion used the global loading path, toggling app-level loading UI. - Cursor history conversion rendered raw internal wrapper payloads as user-visible message text. - Cursor response handling could finalize through overlapping stream/ result paths, and stdout chunk parsing could split JSON lines. Changes - Added non-blocking project refresh plumbing for chat/session flows. - Introduced fetch options in useProjectsState (showLoadingState flag). - Added refreshProjectsSilently() to update metadata without global loading UI. - Wired window.refreshProjects to refreshProjectsSilently in AppContent. - Added Cursor user-message sanitization during history conversion. - Added extractCursorUserQuery() to keep only <user_query> payload. - Added sanitizeCursorUserMessageText() to strip internal wrappers: <user_info>, <agent_skills>, <available_skills>, <environment_context>, <environment_info>. - Applied sanitization only for role === 'user' in convertCursorSessionMessages(). - Hardened Cursor backend stream parsing and finalization. - Added line-buffered stdout parser for chunk-split JSON payloads. - Flushed trailing unterminated stdout line on process close. - Removed redundant content_block_stop emission on Cursor result. - Added frontend duplicate guard in cursor-result handling. - Skips a second assistant bubble when final result text equals already-rendered streamed content. Code comments - Added focused comments describing silent refresh behavior, tag stripping rationale, duplicate guard behavior, and line buffering. Validation - ESLint passes for touched files. - Production build succeeds. Files - server/cursor-cli.js - src/components/app/AppContent.tsx - src/components/chat/hooks/useChatRealtimeHandlers.ts - src/components/chat/utils/messageTransforms.ts - src/hooks/useProjectsState.ts
This commit is contained in:
@@ -692,14 +692,28 @@ export function useChatRealtimeHandlers({
|
||||
const updated = [...previous];
|
||||
const lastIndex = updated.length - 1;
|
||||
const last = updated[lastIndex];
|
||||
const normalizedTextResult = textResult.trim();
|
||||
|
||||
if (last && last.type === 'assistant' && !last.isToolUse && last.isStreaming) {
|
||||
const finalContent =
|
||||
textResult && textResult.trim()
|
||||
normalizedTextResult
|
||||
? textResult
|
||||
: `${last.content || ''}${pendingChunk || ''}`;
|
||||
// Clone the message instead of mutating in place so React can reliably detect state updates.
|
||||
updated[lastIndex] = { ...last, content: finalContent, isStreaming: false };
|
||||
} else if (textResult && textResult.trim()) {
|
||||
} else if (normalizedTextResult) {
|
||||
const lastAssistantText =
|
||||
last && last.type === 'assistant' && !last.isToolUse
|
||||
? String(last.content || '').trim()
|
||||
: '';
|
||||
|
||||
// Cursor can emit the same final text through both streaming and result payloads.
|
||||
// Skip adding a second assistant bubble when the final text is unchanged.
|
||||
const isDuplicateFinalText = lastAssistantText === normalizedTextResult;
|
||||
if (isDuplicateFinalText) {
|
||||
return updated;
|
||||
}
|
||||
|
||||
updated.push({
|
||||
type: resultData.is_error ? 'error' : 'assistant',
|
||||
content: textResult,
|
||||
|
||||
Reference in New Issue
Block a user