Skip to content

Fix UnifiedChat streamed item rendering#479

Merged
AnthonyRonning merged 3 commits intomasterfrom
fix/unified-chat-streaming-ui
Apr 23, 2026
Merged

Fix UnifiedChat streamed item rendering#479
AnthonyRonning merged 3 commits intomasterfrom
fix/unified-chat-streaming-ui

Conversation

@AnthonyRonning
Copy link
Copy Markdown
Contributor

@AnthonyRonning AnthonyRonning commented Apr 16, 2026

Summary

  • switch UnifiedChat tool items to backend-driven streaming semantics
  • preserve streamed reasoning/message content while later lifecycle events arrive
  • only show the fallback Maple loader before the first assistant item to avoid flicker

Validation

  • just format
  • just lint
  • bun run test
  • just build

Summary by CodeRabbit

  • New Features

    • Grouped tool-call rendering with clearer in-progress/completed/error states and merged outputs.
    • Detailed streaming logs and optional debug snapshots to aid diagnostics.
  • Improvements

    • More robust streaming reconciliation for assistant and reasoning content with reliable partial/complete updates.
    • Auto-scroll behavior refined to respect user scrolling while scrolling for new assistant-related items.
    • Loading indicator now shows only until the first assistant message appears.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 16, 2026

📝 Walkthrough

Walkthrough

Refactors UnifiedChat.tsx to introduce ToolCallItem and ToolOutputItem, replace global streaming accumulators with per-(item_id,content_index) delta buffers, normalize/merge streamed reasoning and assistant parts, group tool outputs with their calls for rendering, add SSE logging and adjust auto-scroll/loading behavior.

Changes

Cohort / File(s) Summary
UnifiedChat core
frontend/src/components/UnifiedChat.tsx
Introduced ToolCallItem/ToolOutputItem and MessageStatus types; redefined ExtendedMessage. Added normalization (normalizeConversationItem), upsert/merge helpers (upsertAssistantTextContent, upsertReasoningTextContent, mergeStreamingConversationItem, updateMessageById).
Streaming & buffering
frontend/src/components/UnifiedChat.tsx
Replaced global accumulated content with per-(item_id,content_index) text buffers for assistant & reasoning deltas. Rewrote streaming event handling (processStreamingResponse) to handle response.output_item.added/done, response.*.created, response.failed/cancelled with explicit item lifecycle updates and status propagation.
Tool lifecycle & rendering
frontend/src/components/UnifiedChat.tsx
Tool events now create/update ToolCallItem (in_progresscompleted) and ToolOutputItem. MessageList builds toolCallsByCallId and completedToolCallIds, consumes sequential tool_output items following a tool_call to render grouped toolOutputs via ToolCallRenderer.
UX, autoscroll & logging
frontend/src/components/UnifiedChat.tsx
Loading indicator logic simplified (show until first assistant group item). Added SSE logging helpers (logStreamEvent, summarizeStreamEventForLog), scroll state ref (isUserScrollingRef) and optional debug snapshots; adjusted auto-scroll triggers to include assistant-appended items while respecting user scroll.

Sequence Diagram

sequenceDiagram
    participant Client as Client/WebSocket
    participant StreamHandler as StreamHandler<br/>(processStreamingResponse)
    participant Buffer as DeltaBuffer<br/>(item_id,content_index)
    participant Normalizer as normalizeConversationItem
    participant MessageStore as MessageStore / upsert
    participant MessageList as MessageList
    participant ToolRenderer as ToolCallRenderer

    Client->>StreamHandler: response.output_item.added (tool_call)
    StreamHandler->>Normalizer: normalizeConversationItem(item)
    Normalizer-->>StreamHandler: ToolCallItem
    StreamHandler->>MessageStore: upsert ToolCallItem (status: in_progress)

    Client->>StreamHandler: response.output_item.added (tool_output)
    StreamHandler->>Normalizer: normalizeConversationItem(item)
    Normalizer-->>StreamHandler: ToolOutputItem
    StreamHandler->>MessageStore: upsert ToolOutputItem

    Client->>StreamHandler: response.tool_output.done / tool_call.done
    StreamHandler->>MessageStore: update related ToolCallItem -> completed

    Client->>StreamHandler: response.output_text.delta / reasoning_text.delta
    StreamHandler->>Buffer: append delta to (item_id,content_index)
    Buffer-->>StreamHandler: assembled chunk
    StreamHandler->>MessageStore: mergeStreamingConversationItem -> update content/status

    MessageList->>MessageList: build toolCallsByCallId, pair outputs with preceding call
    MessageList->>ToolRenderer: render grouped toolOutputs + status
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

Poem

🐰 I buffer deltas, nibble each byte,
Pair tool calls with outputs — tidy and bright,
No globals hopping, each item in view,
The rabbit logs streams and scrolls with you 🌿✨

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix UnifiedChat streamed item rendering' accurately describes the main objective of the changeset, which focuses on fixing streamed item rendering in the UnifiedChat component through backend-driven streaming semantics, reasoning preservation, and loader behavior improvements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/unified-chat-streaming-ui

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 16, 2026

Deploying maple with  Cloudflare Pages  Cloudflare Pages

Latest commit: 17f1e4b
Status: ✅  Deploy successful!
Preview URL: https://38a4c841.maple-ca8.pages.dev
Branch Preview URL: https://fix-unified-chat-streaming-u.maple-ca8.pages.dev

View logs

@AnthonyRonning AnthonyRonning force-pushed the fix/unified-chat-streaming-ui branch 2 times, most recently from 26ee483 to fbccf72 Compare April 21, 2026 01:38
AnthonyRonning and others added 2 commits April 20, 2026 23:30
Preserve backend-driven streamed item state so reasoning, tool progress, and loading UI stay stable as SSE lifecycle events arrive.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
The tool_call and tool_output branches of mergeStreamingConversationItem
were hard-coding status to the existing value (or 'in_progress' as
fallback), discarding item.status. This could leave a failing tool call
stuck on a spinner if response.output_item.done carried 'incomplete' or
'error' without a matching tool_output.created follow-up.

Bring both branches in line with the message/reasoning branches by
preferring item.status when provided.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
@AnthonyRonning AnthonyRonning force-pushed the fix/unified-chat-streaming-ui branch from fbccf72 to c941c89 Compare April 21, 2026 04:33
@AnthonyRonning AnthonyRonning marked this pull request as ready for review April 21, 2026 04:35
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
frontend/src/components/UnifiedChat.tsx (1)

2356-2358: Consider removing debug logging in production.

The SSE event logging on every event could be noisy in production. Consider either removing it or wrapping it in a debug flag.

♻️ Suggested change
-      // Log all SSE events for debugging
-      console.log("🔵 SSE Event:", eventType, event);
+      // Uncomment for debugging SSE events:
+      // console.log("🔵 SSE Event:", eventType, event);
🤖 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 2356 - 2358, Remove or
guard the noisy SSE console logging by eliminating the unconditional
console.log("🔵 SSE Event:", eventType, event) in UnifiedChat or wrapping it
behind a debug flag; update the SSE handler where that line appears (search for
the string "🔵 SSE Event:" or the SSE event handler function in UnifiedChat.tsx)
to only log when a debug mode is enabled (e.g., a local DEBUG/REACT_APP_DEBUG
flag or NODE_ENV !== 'production') so production builds do not emit per-event
logs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@frontend/src/components/UnifiedChat.tsx`:
- Around line 2356-2358: Remove or guard the noisy SSE console logging by
eliminating the unconditional console.log("🔵 SSE Event:", eventType, event) in
UnifiedChat or wrapping it behind a debug flag; update the SSE handler where
that line appears (search for the string "🔵 SSE Event:" or the SSE event
handler function in UnifiedChat.tsx) to only log when a debug mode is enabled
(e.g., a local DEBUG/REACT_APP_DEBUG flag or NODE_ENV !== 'production') so
production builds do not emit per-event logs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c051f037-4f99-4133-bb83-0f70e0b1bb46

📥 Commits

Reviewing files that changed from the base of the PR and between a11f0cc and c941c89.

📒 Files selected for processing (1)
  • frontend/src/components/UnifiedChat.tsx

devin-ai-integration[bot]

This comment was marked as resolved.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
frontend/src/components/UnifiedChat.tsx (2)

1138-1169: Consider adding a comment to clarify the loop index modification.

The pattern of modifying index on line 1165 to skip consumed tool outputs works correctly, but this imperative index manipulation within a for loop can be confusing. A brief comment would help future readers understand the intent:

          if (matchedOutputs.length > 0) {
+           // Advance index past consumed tool_output items
            index = nextIndex - 1;
          }
🤖 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 1138 - 1169, The loop
mutates the for-loop index to skip consumed tool output items after rendering a
ToolCall, which is correct but unclear; add a concise inline comment above the
index reassignment explaining that matchedOutputs were consumed and index is
advanced to nextIndex - 1 to avoid re-processing those items (referencing the
variables itemType/"tool_call", ToolCallItem, completedToolCallIds,
renderedToolCall, matchedOutputs, nextIndex, isToolOutputItem, and
ToolCallRenderer) so future readers understand the imperative index
modification.

582-595: Consider adding "searching" to MessageStatus type for consistency.

Line 589 checks for "searching" status, but this value isn't included in the MessageStatus type definition on line 113. While this works at runtime due to the cast on line 585, it creates a type inconsistency.

If "searching" is a valid status for web_search_call items (as used in ToolCallRenderer on lines 858-865), consider adding it to MessageStatus for type safety:

-type MessageStatus = "completed" | "in_progress" | "incomplete" | "streaming" | "error";
+type MessageStatus = "completed" | "in_progress" | "incomplete" | "streaming" | "error" | "searching";
🤖 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 582 - 595, The code
filters for a "searching" status in updateActiveItemStatuses but the
MessageStatus type (MessageStatus union) doesn't include "searching", causing a
type mismatch; update the MessageStatus union/type to include the "searching"
literal so TypeScript recognizes it as valid, and ensure any related enums/types
used by Message, ToolCallRenderer, and web_search_call handling also accept
"searching" to keep consistency across updateActiveItemStatuses,
ToolCallRenderer, and Message definitions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@frontend/src/components/UnifiedChat.tsx`:
- Around line 1138-1169: The loop mutates the for-loop index to skip consumed
tool output items after rendering a ToolCall, which is correct but unclear; add
a concise inline comment above the index reassignment explaining that
matchedOutputs were consumed and index is advanced to nextIndex - 1 to avoid
re-processing those items (referencing the variables itemType/"tool_call",
ToolCallItem, completedToolCallIds, renderedToolCall, matchedOutputs, nextIndex,
isToolOutputItem, and ToolCallRenderer) so future readers understand the
imperative index modification.
- Around line 582-595: The code filters for a "searching" status in
updateActiveItemStatuses but the MessageStatus type (MessageStatus union)
doesn't include "searching", causing a type mismatch; update the MessageStatus
union/type to include the "searching" literal so TypeScript recognizes it as
valid, and ensure any related enums/types used by Message, ToolCallRenderer, and
web_search_call handling also accept "searching" to keep consistency across
updateActiveItemStatuses, ToolCallRenderer, and Message definitions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 35d58dc3-c232-478f-a872-c5be80535fb4

📥 Commits

Reviewing files that changed from the base of the PR and between c941c89 and 17f1e4b.

📒 Files selected for processing (1)
  • frontend/src/components/UnifiedChat.tsx

@AnthonyRonning AnthonyRonning merged commit 51de5cb into master Apr 23, 2026
12 of 13 checks passed
@AnthonyRonning AnthonyRonning deleted the fix/unified-chat-streaming-ui branch April 23, 2026 17:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant