Skip to content

Render paragraph breaks (\n\n) during streaming, not only after#42

Open
infowarrs-tech wants to merge 1 commit into
ddxfish:mainfrom
infowarrs-tech:fix/streaming-paragraph-breaks
Open

Render paragraph breaks (\n\n) during streaming, not only after#42
infowarrs-tech wants to merge 1 commit into
ddxfish:mainfrom
infowarrs-tech:fix/streaming-paragraph-breaks

Conversation

@infowarrs-tech
Copy link
Copy Markdown

@infowarrs-tech infowarrs-tech commented Apr 24, 2026

Problem

I noticed this when using Claude and Mistral, although all LLMs are likely affected. During streaming, \n\n in model output renders as a <br> inside a single <p>, so paragraph spacing doesn't appear until finishStreaming in ui.js swaps the message with a history-rendered version ~500ms after the stream ends. Visible as: paragraph gaps "pop in" at the end of every reply.

Cause

updatePara in interfaces/web/static/ui-streaming.js accumulates all streamed text into state.paraBuf and writes processMarkdown(paraBuf) into a single <p>. The history render at ui-parsing.js:renderContentText splits on /\n\s*\n/ to make real <p> siblings — streaming doesn't.

Fix

Mirror the history render's split inside updatePara. On each update, scan paraBuf for blank-line boundaries; if found, finalize the current <p>, append a new one, and continue. Single newlines remain soft breaks. Runs of 3+ newlines collapse to one split. Trailing empty <p>s are cleaned up by the existing logic in finishStreaming. Tool / think / code accordion handling is untouched.

Testing

Verified locally with Claude API and Mistral API. Also tested with a jsdom harness across: single-chunk multi-break, chunk-split-on-boundary, token-by-token streaming, 3+ newlines, whitespace-only blank lines, trailing breaks. All pass.

The streaming renderer accumulated all assistant output into a single <p>
via paraBuf, with processMarkdown turning blank lines into <br> tags.
Paragraph spacing therefore only appeared at end-of-stream, when
ui.js:finishStreaming swapped the streaming message with a fresh
history-rendered version (which splits on /\n\s*\n/ to make real <p>
siblings).

Mirror that split logic inside updatePara so \n\n in the stream
produces real <p> siblings live, making the swap visually invisible.
Single newlines continue to render as soft breaks; runs of 3+ newlines
collapse to a single split (stray leading whitespace on the new
paragraph is stripped). Trailing empty <p>s are still cleaned up by
finishStreaming as before.

No change to tool/think/code accordion logic.
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