Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions frontend/src/components/UnifiedChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,22 @@ const MessageList = memo(
return { callMap: calls, outputMap: outputs };
}, [messages]);

// Sort priority for assistant items: reasoning first, then tool-related, then messages last.
// This prevents a race condition where SSE events arrive out of order (e.g., assistant message
// "added" event before reasoning "added" event), which would cause thinking blocks to render
// below the "..." loading dots or streamed text.
const assistantItemSortOrder = (item: Message): number => {
if (item.type === "reasoning") return 0;
if (
item.type === "web_search_call" ||
item.type === "function_call" ||
item.type === "function_call_output"
)
return 1;
// "message" items (assistant content / streaming text) come last
return 2;
};

// Group messages into user turns and assistant turns
// Assistant turns include: reasoning, tool calls, tool outputs, web search, and assistant messages
const groupedMessages = useMemo(() => {
Expand All @@ -506,7 +522,9 @@ const MessageList = memo(
if (currentAssistantItems.length > 0) {
groups.push({
type: "assistant",
items: currentAssistantItems,
items: [...currentAssistantItems].sort(
(a, b) => assistantItemSortOrder(a) - assistantItemSortOrder(b)
),
id: `assistant-${currentAssistantItems[0].id}`
});
currentAssistantItems = [];
Comment on lines +525 to 530
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Group ID derived from unsorted array may cause key instability.

The sort result is used for items:, but currentAssistantItems[0].id (lines 528 and 550) references the original unsorted array. If SSE events arrive out of order (the exact scenario this PR fixes), the first item before sorting could be a message item, while after sorting it becomes a reasoning item.

This means the group's React key (assistant-${id}) may reference different item types across renders, potentially causing unnecessary re-renders or subtle reconciliation issues.

Consider using the sorted array's first item for the ID:

🔧 Suggested fix
           if (currentAssistantItems.length > 0) {
+            const sortedItems = [...currentAssistantItems].sort(
+              (a, b) => assistantItemSortOrder(a) - assistantItemSortOrder(b)
+            );
             groups.push({
               type: "assistant",
-              items: [...currentAssistantItems].sort(
-                (a, b) => assistantItemSortOrder(a) - assistantItemSortOrder(b)
-              ),
-              id: `assistant-${currentAssistantItems[0].id}`
+              items: sortedItems,
+              id: `assistant-${sortedItems[0].id}`
             });
             currentAssistantItems = [];
           }

Apply the same pattern for lines 547-551.

Also applies to: 547-551

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/UnifiedChat.tsx` around lines 525 - 530, The group ID
is taken from the unsorted currentAssistantItems[0].id which can differ from the
first item after sorting; update both places where you build the group object
(the block using items:
[...currentAssistantItems].sort((a,b)=>assistantItemSortOrder(a)-assistantItemSortOrder(b))
and the later, identical block) to compute the sorted array into a local
variable (e.g., const sorted = [...currentAssistantItems].sort(...)) and use
sorted[0].id for the group id (`assistant-${sorted[0].id}`) and sorted for
items, then clear currentAssistantItems as before.

Expand All @@ -526,7 +544,9 @@ const MessageList = memo(
if (currentAssistantItems.length > 0) {
groups.push({
type: "assistant",
items: currentAssistantItems,
items: [...currentAssistantItems].sort(
(a, b) => assistantItemSortOrder(a) - assistantItemSortOrder(b)
),
id: `assistant-${currentAssistantItems[0].id}`
});
}
Expand Down
Loading