fix(Messages): stabilize markdown rendering for streaming messages and code blocks#71
Merged
Conversation
Summary: Implement a streaming-safe inline renderer for assistant output that stabilizes pending prose without changing the existing full markdown renderer for committed content. The first version only targets inline code, bold, italic, and inline LaTeX. While an inline construct is still open, show its text content immediately but suppress the opening delimiter(s); once the closing delimiter arrives, re-render that segment with normal markdown formatting. Key Changes: - Add a streaming-specific inline preprocessing step in the assistant message render path, used only for `streamingMessage` and not for committed history. - Keep committed assistant messages rendering through the current markdown pipeline in `src/components/Messages/Messages.tsx` and `src/components/Markdown/Markdown.tsx`. - For the pending assistant tail, derive a display string that: - Detects unmatched inline code delimiters using backticks. - Detects unmatched bold/italic delimiters for `*`, `**`, `_`, and `__`. - Detects unmatched inline LaTeX delimiters matching the existing inline math extension behavior. - Preserves all stable prefix content unchanged. - For an open inline segment, removes the opening delimiter(s) from display but keeps the segment text visible as plain text until the closing delimiter arrives. - Do not change fenced code block streaming behavior in this pass. - Do not add predictive or look-ahead rendering; base behavior only on currently received text. - Keep this logic local to rendering. Do not mutate persisted session messages, stream payloads, or Ollama message contents. - Add a small helper module for inline streaming normalization so the rule set is testable outside the React tree. Rendering Rules: - Inline code: - Closed `` `code` `` renders normally. - Open `` `code`` displays as `code` in plain text. - Bold: - Closed `**bold**` or `__bold__` renders normally. - Open `**bold` or `__bold` displays `bold` in plain text. - Italic: - Closed `*italic*` or `_italic_` renders normally. - Open `*italic` or `_italic` displays `italic` in plain text. - Inline LaTeX: - Closed inline math renders through the existing markdown/math path. - Open inline math displays only the inner text in plain text until closed. - If multiple inline constructs are open, normalize each open suffix region conservatively and avoid trying to nest or repair malformed markdown beyond the supported delimiters. - If parsing is ambiguous, prefer plain visible text over speculative formatting. Implementation Notes: - Introduce a helper such as `normalizeStreamingInlineMarkdown(content: string): string` under the markdown/message area. - Use that helper only when rendering the pending assistant message. - Leave user and system message rendering unchanged. - Keep the existing `parseContent` code-block split behavior intact; apply inline normalization only to text segments, not fenced code block segments. - Reuse the current markdown renderer after normalization rather than creating a second markdown engine. Test Plan: - Unit tests for the normalization helper: - Complete inline code stays unchanged. - Open inline code hides the opening backtick and keeps text visible. - Complete bold/italic stays unchanged. - Open bold/italic hides the opener and keeps text visible. - Complete inline LaTeX stays unchanged. - Open inline LaTeX hides the opener and keeps text visible. - Mixed stable prefix plus open inline suffix preserves the prefix exactly. - Multiple open delimiters degrade conservatively to visible plain text. - Component tests in `src/components/Messages/Messages.test.tsx`: - Pending assistant streaming output uses normalized display for open inline constructs. - Committed assistant messages still render full markdown normally. - User/system messages are unaffected. - Regression tests to ensure fenced code blocks still stream as they do today. Assumptions: - “Inline LaTeX” means the same delimiter behavior supported by the existing inline math extension; this implementation should follow that extension’s accepted inline form rather than invent new math syntax. - The first pass is intentionally limited to inline code, bold, italic, and inline LaTeX. - This pass does not include incremental assistant commits, safe split-point flushing, or debounce; those remain separate follow-up work.
The change is in `src/components/Markdown/Markdown.tsx`: the renderer now creates a width-aware `Marked` instance per render, enables `marked-terminal` reflow, and overrides the `text` renderer so nested inline tokens inside list items are parsed correctly. That removes the raw `**...**` markers that were leaking through in ordered lists.
The problem was in `src/components/CodeBlock/CodeBlock.tsx`: the fence regex only matched code blocks that started at column 0, so indented fences inside lists were left in the markdown text and rendered literally. Changed it to match fences with leading indentation and to strip that shared indentation from the block body before rendering. `src/components/Messages/Messages.tsx` now uses the same normalization when splitting assistant messages into text and code segments.
- unambiguous fences render as code blocks - ambiguous nested same-length fences render as literal raw text instead of broken code UI For raw segments in `src/components/Messages/Messages.tsx`, the renderer: - checks for a complete outer ```markdown... ``` wrapper - strips that wrapper - renders the inner content in CodeBlock with language="markdown" So ambiguous nested markdown examples now show as markdown source without the outer ```markdown fence clutter, while still avoiding reparsing as live markdown.
Codecov Report✅ All modified and coverable lines are covered by tests.
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is the motivation for this pull request?
Bug fix for markdown rendering issues in chat messages, particularly around code blocks and streaming content.
What is the current behavior?
What is the new behavior?
plan.md
Checklist: