Streaming chat UI hardened: tool card ordering, reasoning blocks, MessageList extraction

dropthejase ships a cluster of streaming chat UI fixes - tool-use accordion appearing mid-sentence, tool cards bleeding across turns, edit cards not collapsing on resolve - plus extended thinking support and a `MessageList` extraction. Most of these are specific to this fork's component shape, but two structural fixes apply to any streaming-tools chat UI.

chat-ui

The mid-sentence tool card bug (f757bc8) is the most technically interesting. When tool_call_start arrives, the network may still have buffered content_delta events behind it. The original code called pushEvent() on tool_call_start, which prematurely finalized the in-flight content block and created a second one after the tool accordion. The fix appends the tool_call_start placeholder directly to eventsRef.current without triggering finalization - the real tool event (doc_edited_start etc.) runs pushEvent() and closes the content block at the right time.

PreResponseWrapper's isOpen derivation (af49112) is the other structural fix. The original used useEffect to sync isOpen from props, which introduced a stale-initialization frame. When isStreaming flipped off at the end of a response, the wrapper briefly re-evaluated with stale state and visibly popped open. The fix derives isOpen directly in render: if the user hasn't toggled, isOpen = !shouldMinimize && !hasMinimizedRef.current. No effect needed.

Extended thinking: 7eca5fd enables thinking: { type: 'enabled', budget_tokens: 4096 } on both agents via additionalRequestFields, disables the Strands printer, and adds SSE events (reasoning_delta, reasoning_block_end) to the stream. The ThinkingBlock component cycles through random thinking phrases every 2 seconds rather than sequentially.

MessageList is extracted as a shared component (cbd9640) so both ChatView and ProjectChatPage render messages through the same path. Edit resolution state and reloadingEditIds tracking move into MessageList, cleaning up state that was previously duplicated across both pages. The ref types needed widening to React.RefObject<HTMLDivElement | null> in a follow-up commit (f99b111).

Minor fixes rounded up: read_document spinner stuck forever (3206edf - the agent was emitting the tool result filename rather than looking up the doc from the index); dated title fallback when the LLM ignores the title generation constraint (37c8b1d); always navigate to /assistant after chat delete regardless of whether the deleted chat was active (2b30141).

So what The tool card ordering fix and the `PreResponseWrapper` render-derived `isOpen` are worth reading if you have a streaming-tools chat UI with similar symptoms. The extended thinking SSE events are portable if you're using Strands. The rest is specific to this fork's component hierarchy - importing `MessageList` wholesale only makes sense if you have the same split between `ChatView` and `ProjectChatPage`.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

17 commits from dropthejase/louis, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
2b301415 fix(sidebar): always navigate to /assistant after chat delete Jason Lee 2026-05-12 ↗ GitHub
37c8b1d5 fix(chat): fallback to dated title when LLM returns unhelpful generate-title response; update FEATURE-PARITY Jason Lee 2026-05-12 ↗ GitHub
51d7f384 fix(AssistantChatPage): remove 2s timeout redirect for empty messages Jason Lee 2026-05-12 ↗ GitHub
Page now shows loading state indefinitely; API returns error if chat
truly doesn't exist rather than silently redirecting.
acba2e4f fix(edit-document): align annotation field names with MikeEditAnnotation type Jason Lee 2026-05-12 ↗ GitHub
Rename id→edit_id, del_id→del_w_id, ins_id→ins_w_id so EditCard
accept/reject URLs resolve correctly instead of hitting /edits/undefined/accept.
3206edf0 fix(agent): resolve read_document spinner stuck forever Jason Lee 2026-05-12 ↗ GitHub
commit body
doc_read_start emits real filename; afterToolCallEvent was emitting
filename from result.filename which is always empty (read_document
returns plain text not JSON). Now looks up filename from docIndex
using the tool input doc_id, matching what the start event used.
256b41ba feat(frontend): cycle thinking phrases randomly in ReasoningBlock and ThinkingBlock Jason Lee 2026-05-12 ↗ GitHub
7eca5fd2 feat(agents): enable extended thinking, disable printer, add reasoning SSE events and stream logging Jason Lee 2026-05-12 ↗ GitHub
ee1668eb fix(frontend): collapse tool card immediately when trailing content event is still streaming Jason Lee 2026-05-13 ↗ GitHub
cbd9640d refactor(frontend): extract MessageList shared component for chat message rendering Jason Lee 2026-05-13 ↗ GitHub
commit body
Both ChatView and ProjectChatPage now use MessageList. Edit resolution state
(resolvedEditStatuses, reloadingEditIds) lives in MessageList - ProjectChatPage
no longer has a no-op handleEditResolved stub, so Accept/Reject cards now
collapse correctly in Project Chat.
f99b111a fix(frontend): widen MessageList ref types to accept null Jason Lee 2026-05-13 ↗ GitHub
9afbe9d4 fix(frontend): collapse edit cards on resolve; fix tool card bleed in Project Chat Jason Lee 2026-05-13 ↗ GitHub
commit body
- EditCard returns null when resolved (accepted or rejected)
- EditCardsSection auto-collapses via useEffect when all pending edits resolve
- ProjectChatPage defers getProject refetch to after stream completes to
  prevent mid-stream setProject re-renders that pop tool cards back open
95ff93fc fix(frontend): remove unreachable Accepted/Rejected button labels after early return Jason Lee 2026-05-13 ↗ GitHub
dc7aa1d1 feat(frontend): extract MessageList component; fix edit card collapse and tool card bleed Jason Lee 2026-05-13 ↗ GitHub
af49112e fix(frontend): derive PreResponseWrapper isOpen from props during streaming to eliminate tool card bleed Jason Lee 2026-05-13 ↗ GitHub
4dd0b8a7 fix(frontend): use context deleteChat in ProjectChatPage so sidebar updates immediately on chat deletion Jason Lee 2026-05-13 ↗ GitHub
f757bc84 fix(streaming): prevent tool_call_start from splitting mid-sentence content blocks Jason Lee 2026-05-15 ↗ GitHub
commit body
Two bugs caused the tool-use accordion to appear mid-sentence:
1. tool_call_start incorrectly called pushEvent(), which finalized the
   in-flight content block prematurely before the text was complete.
2. finalizeStreamingContent() only checked the last event; after fix 1
   the last event is the tool_call_start placeholder, so the streaming
   content block (second-to-last) was never committed and the drip text
   was lost.

Fix 1: tool_call_start bypasses pushEvent() and appends the placeholder
directly, leaving the content block open.
Fix 2: finalizeStreamingContent() scans backwards through the event array
to find the streaming content block regardless of its position.
e25bfd80 Merge feature/fix-tool-call-streaming into development Jason Lee 2026-05-15 ↗ GitHub
Fixes tool-use accordion appearing mid-sentence during streaming responses.

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-346.md from inside the repo you want the changes in.

⬇ Download capture-thread-346.md