Skip to content

feat(platform): refine chat streaming UX and simplify marker parser#511

Merged
larryro merged 2 commits into
mainfrom
feat/refine-chat-streaming-ux
Feb 22, 2026
Merged

feat(platform): refine chat streaming UX and simplify marker parser#511
larryro merged 2 commits into
mainfrom
feat/refine-chat-streaming-ux

Conversation

@larryro
Copy link
Copy Markdown
Collaborator

@larryro larryro commented Feb 22, 2026

Summary

  • Rewrite auto-scroll to use wheel/touch events instead of scrollTop comparison, preventing false positives from nested scrollable containers and browser scroll clamping
  • Fix double-cursor rendering in incremental markdown by detecting cursor-eligible child nodes
  • Simplify thinking animation to hide when text is actively streaming
  • Reduce marker parser to only split on [[NEXT_STEPS]] — all other markers are stripped so content renders as plain markdown
  • Remove unused structured section components (Conclusion, KeyPoints, Details, Questions) and their translations

Test plan

  • Added incremental-markdown.test.tsx covering single cursor guarantee for paragraphs, loose lists, code blocks, and cursor-hidden state
  • Updated marker-parser.test.ts reflecting simplified split-on-NEXT_STEPS-only behavior
  • Verify auto-scroll behavior during streaming: scroll should stick when at bottom, and user scroll-up should pause auto-scroll
  • Verify thinking animation hides once content starts streaming
  • Verify no double cursors appear during markdown rendering

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved auto-scroll detection to better recognize user scrolling intent and handle nested scrollable containers
    • Fixed thinking animation to not display when text is actively streaming
  • New Features

    • Enhanced streaming content rendering with improved cursor stability and consistency
  • Changes

    • Simplified structured message sections; now displays only follow-up suggestions instead of multiple section types
  • Tests

    • Added comprehensive test coverage for cursor rendering and streaming content stability

Rewrite auto-scroll to use wheel/touch events instead of scrollTop
comparison, preventing false positives from nested scrollable containers
and browser scroll clamping. Fix double-cursor rendering in incremental
markdown by detecting cursor-eligible child nodes. Simplify thinking
animation to hide when text is actively streaming. Reduce marker parser
to only split on [[NEXT_STEPS]] — all other markers are stripped so
content renders as plain markdown. Remove unused structured section
components (Conclusion, KeyPoints, Details, Questions) and their
translations.
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Feb 22, 2026

Greptile Summary

This PR improves chat streaming UX across several areas: auto-scroll detection is rewritten to use wheel/touch events instead of scrollTop comparison (preventing false positives from nested scrollable containers), double-cursor rendering is fixed in incremental markdown, the thinking animation is simplified to hide when text is actively streaming, and the marker parser is reduced to only split on [[NEXT_STEPS]] while stripping other markers as plain markdown.

  • Auto-scroll rewrite (use-auto-scroll.ts): Replaces scroll-position-based detection with direct input events (wheel, touch), adds nested container scroll absorption check, and adds a deferred final scroll when streaming ends. Note: keyboard scrolling (Page Up/arrow keys) won't disable auto-scroll, which may be an acceptable trade-off.
  • Double-cursor fix (incremental-markdown.tsx): Adds CURSOR_ELIGIBLE_TAGS set and hasCursorEligibleChild detection to prevent parent elements from rendering a cursor when their block-level children already handle it. Also adds pre as a cursor-eligible tag. Well-covered by new tests.
  • Thinking animation simplification (thinking-animation.tsx, chat-messages.tsx): The complex multi-state condition is replaced by a single hasIncompleteAssistantMessage flag, and the animation hides itself when text is streaming.
  • hasIncompleteAssistantMessage refactor (use-message-processing.ts): Changed from checking the last assistant message's status to checking the newest message by _creationTime. This changes behavior when only user messages exist (now returns true instead of false), which breaks an existing unit test that was not updated.
  • Marker parser simplification (marker-parser.ts): Only [[NEXT_STEPS]] creates a structural split; all other markers are stripped. Clean implementation with updated tests.
  • Dead code: pendingToolResponse, hasActiveTools, and isProcessingToolResult are still computed and returned from useMessageProcessing but are no longer consumed anywhere.

Confidence Score: 3/5

  • Generally safe to merge but has a behavior change that breaks an existing unit test and leaves dead code in the hook.
  • The PR is well-structured with good documentation and test coverage for the new features. However, the hasIncompleteAssistantMessage refactor changes behavior in a way that breaks an existing test (line 73 of use-message-processing.test.ts expects false for user-only messages, but the new logic returns true). Additionally, three computed values (pendingToolResponse, hasActiveTools, isProcessingToolResult) are now dead code — still computed but never consumed. The auto-scroll rewrite and cursor fix are solid improvements.
  • Pay close attention to services/platform/app/features/chat/hooks/use-message-processing.ts — the hasIncompleteAssistantMessage logic change breaks an existing test and may have unintended UI side effects.

Important Files Changed

Filename Overview
services/platform/app/hooks/use-auto-scroll.ts Major rewrite: replaces scrollTop-based detection with wheel/touch events and nested container checks. Well-documented with clear comments. Logic is sound — only concern is that keyboard scrolling (Page Up, arrow keys) won't disable auto-scroll, but this is likely acceptable.
services/platform/app/features/chat/components/incremental-markdown.tsx Fixes double-cursor rendering by adding CURSOR_ELIGIBLE_TAGS set and hasCursorEligibleChild detection. Also adds pre to cursor-eligible tags and removes consolidation on stream end to prevent scroll jumps. Well-tested changes.
services/platform/app/features/chat/components/tests/incremental-markdown.test.tsx New comprehensive test file covering cursor injection (single cursor guarantee for paragraphs, loose lists, tight lists, headings, tables, code blocks) and split stability (scroll jump prevention). Good coverage of the double-cursor bug fix.
services/platform/lib/utils/marker-parser.ts Simplified to only split on [[NEXT_STEPS]] while stripping all other markers. Logic is clean and well-documented. Tests updated to match.
services/platform/lib/utils/marker-parser.test.ts Tests rewritten to reflect simplified parser behavior. Good coverage of stripped markers, NEXT_STEPS splitting, streaming partial-marker buffering, and progressive streaming simulation.
services/platform/app/features/chat/components/thinking-animation.tsx Simple addition: returns null when text is streaming and no tools are running, hiding the thinking indicator when the user can see text progress directly.
services/platform/app/features/chat/hooks/use-message-processing.ts Refactored hasIncompleteAssistantMessage to use newest message by _creationTime instead of last assistant message. Behavior change breaks existing test ("returns false when there are no assistant messages"). Also leaves dead code: pendingToolResponse, hasActiveTools, isProcessingToolResult are still computed but no longer consumed.
services/platform/app/features/chat/components/chat-interface.tsx Removes destructuring of pendingToolResponse, hasActiveTools, isProcessingToolResult, and isPending prop passing. Clean removal aligned with the simplified thinking animation logic.
services/platform/app/features/chat/components/chat-messages.tsx Simplifies thinking animation condition from a complex multi-state check to just hasIncompleteAssistantMessage. Removes unused props from interface and function signature.
services/platform/app/features/chat/components/structured-message/structured-message.tsx Removes CONCLUSION, KEY_POINTS, DETAILS, QUESTIONS section rendering from the switch statement, keeping only NEXT_STEPS. Clean removal consistent with marker parser simplification.
services/platform/app/features/chat/components/structured-message/section-renderers.tsx Large deletion of unused section components (ConclusionSection, KeyPointsSection, DetailsSection, QuestionsSection) and their shared props/imports. Only NextStepsSection remains.
services/platform/messages/en.json Removes 5 unused translation keys corresponding to deleted section renderers (conclusion, keyPoints, showDetails, hideDetails, questions).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User sends message] --> B{hasIncompleteAssistantMessage?}
    B -->|true| C[Show ThinkingAnimation]
    C --> D{streamingMessage.text && no tools?}
    D -->|yes| E[ThinkingAnimation returns null]
    D -->|no| F[Show thinking indicator with tool details]
    E --> G[User sees streaming text with cursor]
    G --> H{Content growing?}
    H -->|yes| I{autoScrollEnabled?}
    I -->|yes| J[ResizeObserver triggers scrollTo bottom]
    I -->|no| K[No scroll - user scrolled away]
    H -->|no| L[No scroll action]
    
    M[User scrolls up via wheel/touch] --> N{Nested container can absorb?}
    N -->|yes| O[Ignore - nested scroll]
    N -->|no| P[Disable auto-scroll]
    
    Q[User reaches bottom] --> R[Re-enable auto-scroll]
    
    S[Streaming ends] --> T{wasEnabled && autoScrollEnabled?}
    T -->|yes| U[One deferred scroll via rAF]
    T -->|no| V[No action]
    
    B -->|false - assistant completed| W[No ThinkingAnimation]
Loading

Last reviewed commit: 4b6792a

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

12 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment thread services/platform/app/features/chat/hooks/use-message-processing.ts
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Feb 22, 2026

Additional Comments (1)

services/platform/app/features/chat/hooks/use-message-processing.ts
Dead code: pendingToolResponse, hasActiveTools, isProcessingToolResult

These values are still computed (lines 155-203) and returned (lines 229-231) from the hook, but are no longer consumed by any component after this PR removes them from chat-interface.tsx and chat-messages.tsx. They also remain in the UseMessageProcessingResult interface (lines 50-52). Consider removing the unused computations, return values, and interface fields to avoid dead code and unnecessary useMemo calls on every render.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 22, 2026

📝 Walkthrough

Walkthrough

The PR refactors chat message handling by removing tool-response UI components and simplifying structured message rendering to support only NEXT_STEPS sections. It improves streaming cursor stability by introducing nesting checks to prevent duplicate cursors in block-level elements, replaces scroll-top heuristics with input-driven auto-scroll detection, and updates loading-state logic to check the newest message by creation time. Tool-related props are removed from ChatMessages, TypewriterText-based rendering is eliminated for non-NEXT_STEPS sections, and related translation keys are removed.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary changes: refining chat streaming UX (auto-scroll, thinking animation, cursor rendering) and simplifying the marker parser (from multi-marker to NEXT_STEPS only).
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/refine-chat-streaming-ux

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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

When only user messages exist with no assistant reply, the thread is
waiting for a response so hasIncompleteAssistantMessage should be true.
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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@services/platform/app/features/chat/components/__tests__/incremental-markdown.test.tsx`:
- Around line 10-13: The test's countCursors function uses a too-broad selector
('[aria-hidden="true"]'); narrow it to target the blinking cursor element
specifically (e.g., select the span with the cursor class or a test id) so other
hidden markdown nodes aren't counted — update countCursors to
querySelectorAll('span.cursor[aria-hidden="true"]') or
'span[data-testid="cursor"][aria-hidden="true"]' (or whatever class/testid your
cursor element uses) to reliably count only the cursor element.

In `@services/platform/app/features/chat/hooks/use-message-processing.ts`:
- Around line 212-214: The reduce call that computes newestMessage from
uiMessages is brittle; replace it with a clearer, safer approach such as sorting
a shallow copy (e.g. use [...uiMessages].sort((a,b)=>b._creationTime -
a._creationTime)[0]) or call reduce with an explicit initial value to avoid any
ambiguity; update the logic around newestMessage and keep references to
uiMessages and the _creationTime comparison so the code remains correct and
still respects the existing early-return guard.

In `@services/platform/app/hooks/use-auto-scroll.ts`:
- Around line 121-156: The auto-scroll disabling currently only handles
wheel/touch events (handleWheel, handleTouchMove) so scrollbar drags and
keyboard scrolling don't flip autoScrollEnabledRef; update the hook to also
detect pointer/keyboard intent by adding listeners for pointerdown and keydown
that set autoScrollEnabledRef.current = false when user interaction is detected
(use canNestedContainerScrollUp(container) check like in handleWheel), and add a
programmaticScrollRef guard that you set true/false around any internal
programmatic scroll calls so handleScroll can ignore those and only re-enable
auto-scroll via isAtBottom() when programmaticScrollRef is false; ensure you
clean up the new listeners and use the existing container, handleScroll, and
canNestedContainerScrollUp helpers.

Comment thread services/platform/app/features/chat/hooks/use-message-processing.ts
Comment thread services/platform/app/hooks/use-auto-scroll.ts
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