Merge pull request #5813 from nesquena/release/stage-5773

Release: keep live reasoning on one renderer path (#5773)
This commit is contained in:
nesquena-hermes authored and GitHub committed 2026-07-08 20:16:18 -07:00
commit 4111e097c8
5 files changed
+49 -5

No files matched your search

+2
View File
@@ -5,6 +5,8 @@
### Fixed ### Fixed
- **Live reasoning no longer briefly renders twice while a reply streams.** The streaming path updated the reasoning display through two renderers at once (the anchored reasoning block and the live thinking card), which could momentarily show the reasoning twice. It now routes through a single renderer — the live thinking card only updates when the anchored path didn't handle it. Thanks @rodboev. (#5773, #5720)
- **A wide or tall code block in the chat no longer thrashes the surrounding layout while a reply streams.** Code blocks now use CSS `contain: content`, so a growing/overflowing `<pre>` is isolated to its own rounded container (with its own horizontal scroll) instead of reflowing the message column during streaming. Thanks @rodboev. (#5770, #5760) - **A wide or tall code block in the chat no longer thrashes the surrounding layout while a reply streams.** Code blocks now use CSS `contain: content`, so a growing/overflowing `<pre>` is isolated to its own rounded container (with its own horizontal scroll) instead of reflowing the message column during streaming. Thanks @rodboev. (#5770, #5760)
- **Workspace switcher is now navigable with a screen reader.** The workspace switcher exposes proper roles/labels and announces the "New Chat" workspace state transiently, and the closed workspace dropdown is kept out of screen-reader navigation so it isn't read while collapsed. Visual layout and click/keyboard behavior are unchanged. Thanks @rodboev. (#5721, #5671) - **Workspace switcher is now navigable with a screen reader.** The workspace switcher exposes proper roles/labels and announces the "New Chat" workspace state transiently, and the closed workspace dropdown is kept out of screen-reader navigation so it isn't read while collapsed. Visual layout and click/keyboard behavior are unchanged. Thanks @rodboev. (#5721, #5671)
+4 -2
View File
@@ -5024,8 +5024,10 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
if(d.text&&S.session&&S.session.session_id===activeSid) _completeAutomaticCompressionOnLiveProgress(activeSid); if(d.text&&S.session&&S.session.session_id===activeSid) _completeAutomaticCompressionOnLiveProgress(activeSid);
syncInflightAssistantMessage(); syncInflightAssistantMessage();
if(text&&S.session&&S.session.session_id===activeSid){ if(text&&S.session&&S.session.session_id===activeSid){
_upsertAnchorReasoning(_liveThinkingText()); const liveThinkingText=_liveThinkingText();
_updateLiveThinkingCard(_liveThinkingText()); if(!_upsertAnchorReasoning(liveThinkingText)){
_updateLiveThinkingCard(liveThinkingText);
}
} }
}); });
+6 -2
View File
@@ -931,8 +931,12 @@ def test_messages_js_supports_live_reasoning_and_tool_completion(cleanup_test_se
"messages.js must listen for live reasoning SSE events" "messages.js must listen for live reasoning SSE events"
assert "liveReasoningText += text" in src, \ assert "liveReasoningText += text" in src, \
"live reasoning SSE events must update the active Worklog Thinking Card text" "live reasoning SSE events must update the active Worklog Thinking Card text"
assert "_updateLiveThinkingCard(_liveThinkingText())" in src, \ assert "const liveThinkingText=_liveThinkingText();" in src, \
"live reasoning SSE events must refresh the current segment's Worklog Thinking Card" "live reasoning SSE events must compute the current segment's Worklog Thinking Card text once"
assert "if(!_upsertAnchorReasoning(liveThinkingText))" in src, \
"live reasoning SSE events must prefer the anchor renderer before falling back"
assert "_updateLiveThinkingCard(liveThinkingText)" in src, \
"live reasoning SSE events must keep the current segment's Worklog Thinking Card as fallback"
assert "source.addEventListener('tool_complete'" in src or 'source.addEventListener("tool_complete"' in src, \ assert "source.addEventListener('tool_complete'" in src or 'source.addEventListener("tool_complete"' in src, \
"messages.js must listen for live tool completion SSE events" "messages.js must listen for live tool completion SSE events"
assert "function _parseStreamState()" in src, \ assert "function _parseStreamState()" in src, \
@@ -1306,7 +1306,9 @@ def test_slice6_live_shadow_feed_wires_anchor_scene_for_visible_order_handoff():
assert "_upsertAnchorProcessProse(displayText" in src assert "_upsertAnchorProcessProse(displayText" in src
reasoning_body = _event_listener_body(src, "reasoning") reasoning_body = _event_listener_body(src, "reasoning")
assert "_applyToAnchor" not in reasoning_body assert "_applyToAnchor" not in reasoning_body
assert "_upsertAnchorReasoning(_liveThinkingText())" in reasoning_body assert "const liveThinkingText=_liveThinkingText();" in reasoning_body
assert "if(!_upsertAnchorReasoning(liveThinkingText))" in reasoning_body
assert "_updateLiveThinkingCard(liveThinkingText)" in reasoning_body
assert "function _flushReasoningToAnchor()" in src assert "function _flushReasoningToAnchor()" in src
assert "_upsertAnchorReasoning(reasoningText" in src assert "_upsertAnchorReasoning(reasoningText" in src
assert "`live-reasoning:${streamId}:final`" in src assert "`live-reasoning:${streamId}:final`" in src
+34
View File
@@ -670,6 +670,40 @@ class TestToolCallGroupingStatic:
"Tool starts must not split consecutive tools into one-tool Activity rows." "Tool starts must not split consecutive tools into one-tool Activity rows."
) )
def test_reasoning_stream_uses_one_live_renderer_path(self):
reasoning_match = re.search(
r"source\.addEventListener\('reasoning',e=>\{(.*?)\n\s*\}\);",
MESSAGES_JS,
re.S,
)
assert reasoning_match, "reasoning listener not found"
reasoning_fn = reasoning_match.group(1)
render_live_thinking_fn = _function_body(MESSAGES_JS, "_renderLiveThinking")
assert reasoning_fn.count("_liveThinkingText()") == 1, (
"_liveThinkingText() should be computed once inside the active-session branch."
)
assert "const liveThinkingText=_liveThinkingText();" in reasoning_fn, (
"Reasoning SSE updates should cache the live thinking text before routing."
)
assert "_upsertAnchorReasoning(liveThinkingText)" in reasoning_fn, (
"Anchor reasoning must remain the primary renderer path."
)
assert reasoning_fn.index("_upsertAnchorReasoning(liveThinkingText)") < reasoning_fn.index(
"_updateLiveThinkingCard(liveThinkingText)"
), (
"The legacy thinking card should only run after anchor upsert fails."
)
assert "if(!_upsertAnchorReasoning(liveThinkingText)){" in reasoning_fn, (
"The legacy thinking card should be a falsy-anchor fallback."
)
assert reasoning_fn.count("_updateLiveThinkingCard(liveThinkingText)") == 1, (
"Reasoning SSE updates should call the live thinking card only in fallback."
)
assert "_updateLiveThinkingCard(" in render_live_thinking_fn, (
"Inline parsed thinking still needs the live thinking card renderer."
)
def test_live_thinking_card_is_segment_scoped_not_global_singleton(self): def test_live_thinking_card_is_segment_scoped_not_global_singleton(self):
live_thinking_fn = _function_body(UI_JS, "appendThinking") live_thinking_fn = _function_body(UI_JS, "appendThinking")
placement_fn = _function_body(MESSAGES_JS, "_liveThinkingPlacement") placement_fn = _function_body(MESSAGES_JS, "_liveThinkingPlacement")