Skip to content

fix(ios): assistant chat replies collapse multi-line responses into one line#98056

Closed
Sanjays2402 wants to merge 1 commit into
openclaw:mainfrom
Sanjays2402:fix/issue-98028
Closed

fix(ios): assistant chat replies collapse multi-line responses into one line#98056
Sanjays2402 wants to merge 1 commit into
openclaw:mainfrom
Sanjays2402:fix/issue-98028

Conversation

@Sanjays2402

Copy link
Copy Markdown
Contributor

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 \n in 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 through AttributedString(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:) using interpretedSyntax: .full, which follows the CommonMark spec; CommonMark treats a solo \n inside 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 through ChatMarkdownPreprocessor.preprocess unchanged because the transform lives in ChatMarkdownRenderer.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):

input:    "hello\nworld"
base out: "hello world"  (no \n, bug)
fix out:  "hello\nworld" (newline preserved)

ChatMarkdownRendererTests (Swift Testing) added in this PR cover:

  • preserves single newlines between assistant lines: round-trips "hello\nworld" through parsedMarkdown and asserts the rendered characters still contain \n (fails on main, 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 lines and leaves backslash hard breaks alone.
  • passes through single-line and empty inputs.
  • keeps unordered list semantics intact: list-item runs still carry listItem presentation intent after the transform.
  • keeps header followed by paragraph as header: # Heading\nBody still parses the header as header 1.

Scope is contained: only ChatMarkdownRenderer.swift (logic) and the new ChatMarkdownRendererTests.swift.

 apps/.../OpenClawChatUI/ChatMarkdownRenderer.swift | 72 ++++++++++++++++++-
 .../ChatMarkdownRendererTests.swift                | 87 ++++++++++++++++++++++
 2 files changed, 158 insertions(+), 1 deletion(-)

AI Disclosure

Written with the assistance of Claude. I have reviewed the diff and the test and take responsibility for the code.

…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
@clawsweeper

clawsweeper Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed June 30, 2026, 3:02 AM ET / 07:02 UTC.

Summary
The PR routes chat Markdown through a new renderer helper that injects CommonMark hard breaks before AttributedString(markdown:) and adds Swift tests for the helper.

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.

  • Native build checks: 2 failed. The relevant macOS Swift and iOS build lanes both fail on the changed renderer, so maintainers should treat this patch as non-buildable before behavior review.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #98028
Summary: This PR is a candidate fix for the canonical iOS assistant line-break rendering bug, with one sibling candidate PR still open for the same issue.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🧂 unranked krab
Proof: 🦪 silver shellfish
Patch quality: 🧂 unranked krab
Result: blocked until stronger real behavior proof is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P1] Fix the Swift actor-isolation build error in ChatMarkdownRenderer.
  • [P1] Add coverage for indented code blocks and longer fenced code blocks before hard-break insertion.
  • Attach redacted iOS simulator or device proof showing multiline assistant replies render correctly.

Proof guidance:

  • [P1] Needs stronger real behavior proof before merge: The PR body includes copied parser output, but it does not show after-fix iOS chat UI behavior; add redacted simulator/device proof and update the PR body to trigger a fresh review, or ask a maintainer for @clawsweeper re-review.

Risk before merge

  • [P1] No after-fix iOS simulator/device proof is attached; the copied parser output does not show the visible chat bubble behavior users reported.
  • [P1] The branch overlaps with fix(ios): preserve assistant line breaks in chat #98054, so maintainers should avoid landing two competing newline transforms for the same renderer path.

Maintainer options:

  1. Decide the mitigation before merge
    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.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P1] Contributor or maintainer follow-up is needed because the branch has a compile blocker, a Markdown code-block regression, and missing iOS visual proof; automation should not repair this without contributor proof.

Security
Cleared: The diff changes Swift chat rendering code and Swift tests only; it does not touch dependencies, workflows, secrets, package resolution, install scripts, or other supply-chain surfaces.

Review findings

  • [P1] Make the fence helper nonisolated — apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownRenderer.swift:78
  • [P2] Preserve code-block Markdown before adding breaks — apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownRenderer.swift:108
Review details

Best 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:

  • [P1] Make the fence helper nonisolated — apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownRenderer.swift:78
    preserveSoftLineBreaks is declared nonisolated, but it calls fenceMarker, which remains main-actor-isolated because it lives inside the @MainActor view type. The PR merge ref fails both macOS Swift and iOS builds at this call, so the branch cannot compile until the helper is made nonisolated or moved out of the actor-isolated type.
    Confidence: 0.98
  • [P2] Preserve code-block Markdown before adding breaks — apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownRenderer.swift:108
    This transform appends hard-break spaces to every nonblank, non-fenced line, but it does not recognize indented code blocks or CommonMark fences longer than three markers. That can mutate literal code lines or close a quadruple-backtick fence on an inner triple-backtick line before adding breaks to the rest of the code block; skip indented code and track fence marker length before appending spaces.
    Confidence: 0.9

Overall correctness: patch is incorrect
Overall confidence: 0.93

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 56c2d637d940.

Label changes

Label changes:

  • add P2: The PR targets a shipped iOS chat readability bug with limited blast radius and no data-loss, security, or availability impact.
  • add rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🦪 silver shellfish and patch quality is 🧂 unranked krab.
  • add status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The PR body includes copied parser output, but it does not show after-fix iOS chat UI behavior; add redacted simulator/device proof and update the PR body to trigger a fresh review, or ask a maintainer for @clawsweeper re-review.

Label justifications:

  • P2: The PR targets a shipped iOS chat readability bug with limited blast radius and no data-loss, security, or availability impact.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🦪 silver shellfish and patch quality is 🧂 unranked krab.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The PR body includes copied parser output, but it does not show after-fix iOS chat UI behavior; add redacted simulator/device proof and update the PR body to trigger a fresh review, or ask a maintainer for @clawsweeper re-review.
Evidence reviewed

PR surface:

Other +157. Total +157 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 0 0 0 0
Tests 0 0 0 0
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 2 158 1 +157
Total 2 158 1 +157

What I checked:

Likely related people:

  • masatohoshino: Authored the recent merged PR whose commit added the current shared ChatMarkdownRenderer, ChatMarkdownPreprocessor, and related chat UI files now touched by this fix. (role: recent area contributor; confidence: high; commits: 888f399499c4; files: apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownRenderer.swift, apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownPreprocessor.swift, apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMessageViews.swift)
  • vincentkoc: Merged the PR that introduced the current renderer files and also merged recent adjacent iOS chat Dynamic Type work touching the same chat rendering surface. (role: recent merger; confidence: medium; commits: 888f399499c4, 62eb13b24687; files: apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownRenderer.swift, apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMessageViews.swift)
  • jmcte: Authored the merged iOS chat Dynamic Type change that updated nearby chat text call sites shortly before this report. (role: recent adjacent contributor; confidence: medium; commits: 62eb13b24687; files: apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMessageViews.swift, apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatView.swift, apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatComposer.swift)
  • mbelinky: Authored and merged earlier iOS chat UI cleanup that modified ChatMarkdownPreprocessor and ChatMessageViews in this shared native chat surface. (role: feature-area contributor; confidence: medium; commits: 9476dda9f617, b6914727ee14; files: apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownPreprocessor.swift, apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMessageViews.swift)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. labels Jun 30, 2026
@joshavant

Copy link
Copy Markdown
Contributor

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.

@joshavant joshavant closed this Jul 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal backlog priority with limited blast radius. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: S status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: iOS app: Assistant response line breaks not rendered

2 participants