fix(ios): assistant chat replies collapse multi-line responses into one line#98056
fix(ios): assistant chat replies collapse multi-line responses into one line#98056Sanjays2402 wants to merge 1 commit into
Conversation
…ne line Foundation's CommonMark parser turns a solo \n into a space when AttributedString(markdown:) runs with interpretedSyntax: .full, so any assistant message that arrived with line-by-line formatting was rendered as a single wrapped line in the iOS chat bubble. Pre-process the cleaned markdown to append two trailing spaces to every intra-paragraph newline (CommonMark hard break) before parsing; blank lines stay paragraph separators and fenced code blocks are left untouched so their literal layout survives verbatim. User messages still go through ChatMarkdownPreprocessor.preprocess unchanged because the transform now lives in ChatMarkdownRenderer.parsedMarkdown. Adds ChatMarkdownRendererTests covering the issue openclaw#98028 repro, list / header / blockquote / paragraph / code-fence semantics, already hard-broken lines, backslash hard breaks, and empty / single-line inputs. Fixes openclaw#98028
|
Codex review: needs real behavior proof before merge. Reviewed June 30, 2026, 3:02 AM ET / 07:02 UTC. Summary PR surface: Other +157. Total +157 across 2 files. Reproducibility: no. high-confidence live reproduction was run in this read-only review. The linked issue has screenshots and current main shows assistant text reaching the Swift Markdown renderer without newline-specific handling, so the path is source-reproducible with medium confidence. Review metrics: 1 noteworthy metric.
Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Security Review findings
Review detailsBest possible solution: Land one narrow renderer-level fix that compiles, preserves CommonMark code blocks and fences, and includes redacted iOS simulator or device proof for multiline assistant replies. Do we have a high-confidence way to reproduce the issue? No high-confidence live reproduction was run in this read-only review. The linked issue has screenshots and current main shows assistant text reaching the Swift Markdown renderer without newline-specific handling, so the path is source-reproducible with medium confidence. Is this the best way to solve the issue? No, not as submitted: the renderer layer is plausible, but this implementation must compile and preserve CommonMark fence and indented-code behavior. A safer fix should reuse the narrower rendering boundary while adding the missing code-block cases and visible iOS proof. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 56c2d637d940. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Other +157. Total +157 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
Thanks @Sanjays2402 for working on this. The issue has been fixed by #98304, merged as commit 47845e1. This PR was directionally close because it put the fix in the renderer path, but the final landed patch addresses the same root cause with build-safe helper placement, broader Markdown edge-case coverage, and live iOS simulator proof. Closing this as superseded by #98304. |
Closes #98028
What Problem This Solves
Fixes an issue where users sending or receiving multi-line assistant replies in the iOS app would see the entire response render as one wrapped line, even when the message was emitted line-by-line. Every
\nin the assistant bubble collapsed into a single space, so structured responses (steps, paragraphs, blockquotes typed out line-by-line) lost their layout and became visually unreadable. The macOS app does not show this because it does not route assistant text throughAttributedString(markdown:). Reported on iOS 18.5 across multiple models (Sonnet 3.7, GPT-4.5, Gemini 2.5 Flash, GLM 4.5).Why This Change Was Made
The iOS chat bubble parses assistant markdown with
AttributedString(markdown:options:)usinginterpretedSyntax: .full, which follows the CommonMark spec; CommonMark treats a solo\ninside a paragraph as a soft line break and renders it as a space. The fix pre-processes the cleaned markdown before parsing and appends two trailing spaces (the CommonMark hard-break marker) to every intra-paragraph newline. Blank lines remain paragraph separators, fenced code blocks are left verbatim so their literal layout survives, and lines that already end in two spaces or a backslash hard break are not touched. User messages still flow throughChatMarkdownPreprocessor.preprocessunchanged because the transform lives inChatMarkdownRenderer.parsedMarkdown, not in the shared preprocessor.User Impact
Multi-line assistant responses in the iOS chat now render with the same line layout the model produced. Lists, headers, blockquotes, fenced code, and paragraph breaks behave exactly as before; only the previously-invisible intra-paragraph newlines become visible hard breaks.
Evidence
Local reproduction of the underlying parser behavior on macOS 26.5 with Swift 6.2 (mirrors the iOS Foundation path):
ChatMarkdownRendererTests(Swift Testing) added in this PR cover:preserves single newlines between assistant lines: round-trips"hello\nworld"throughparsedMarkdownand asserts the rendered characters still contain\n(fails onmain, passes with this change).preserves multiple solo newlines across lines: three-line input round-trips intact.injects hard breaks for intra-paragraph newlines:preserveSoftLineBreaks(in:)produces"hello \nworld".keeps blank line paragraph separators:"Para1\n\nPara2"is left untouched (no spurious hard break).leaves fenced code blocks untouched: content between ``` fences is byte-for-byte unchanged.does not double-break already hard-broken linesandleaves backslash hard breaks alone.passes through single-line and empty inputs.keeps unordered list semantics intact: list-item runs still carrylistItempresentation intent after the transform.keeps header followed by paragraph as header:# Heading\nBodystill parses the header asheader 1.Scope is contained: only
ChatMarkdownRenderer.swift(logic) and the newChatMarkdownRendererTests.swift.AI Disclosure
Written with the assistance of Claude. I have reviewed the diff and the test and take responsibility for the code.