Skip to content

fix(ios): preserve assistant line breaks in chat#98054

Closed
qingminglong wants to merge 1 commit into
openclaw:mainfrom
qingminglong:codex/98028-ios-line-breaks
Closed

fix(ios): preserve assistant line breaks in chat#98054
qingminglong wants to merge 1 commit into
openclaw:mainfrom
qingminglong:codex/98028-ios-line-breaks

Conversation

@qingminglong

@qingminglong qingminglong commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Closes #98028

What Problem This Solves

Fixes an issue where iOS users reading assistant responses would see multi-line replies flattened into one continuous paragraph when the assistant text contained visible single line breaks.

Why This Change Was Made

The shared native chat renderer now converts visible single newlines in cleaned display text into Markdown hard breaks before Swift Markdown parsing, while leaving paragraph breaks, existing hard breaks, escaped line breaks, fenced code blocks, and indented code blocks unchanged. This keeps the fix local to the chat Markdown rendering path without changing assistant text extraction or provider/gateway behavior.

User Impact

Assistant responses in the iOS chat can display intentional line breaks as separate lines, matching the readable structure users expect from multi-line replies.

Evidence

  • Claim proved: the renderer receives a Markdown-safe display string that preserves visible prose line breaks without mutating common code-block Markdown forms.
  • Commands / artifacts:
    • git diff --check passed.
    • python .agents\skills\autoreview\scripts\autoreview --mode local first reported an indented-code-block mutation risk; fixed in this branch.
    • python .agents\skills\autoreview\scripts\autoreview --mode local second reported a longer-fence Markdown mutation risk; fixed in this branch.
    • python .agents\skills\autoreview\scripts\autoreview --mode local final run: autoreview clean: no accepted/actionable findings reported.
    • swift test --package-path apps/shared/OpenClawKit --filter ChatMarkdownPreprocessorTests attempted locally but Windows does not have swift installed (swift : The term 'swift' is not recognized...).
    • swift test --package-path apps/shared/OpenClawKit --filter ChatEventTextTests attempted locally but Windows does not have swift installed (swift : The term 'swift' is not recognized...).
    • GitHub Actions iOS Simulator proof workflow passed: https://github.com/qingminglong/openclaw/actions/runs/28437348097
    • Proof artifact: ios-chat-line-break-proof-pr-98054 (artifact id 7977787938, 211269 bytes) from workflow run 28437348097.
    • Proof screenshot file inside the artifact: iPhone 17-ios-chat-line-break-proof.png.
    • Proof run target SHA: 92531758b3081fd314e4f76a0f74523af7f2606f.
  • Observed result: the iPhone 17 simulator screenshot shows the assistant fixture rendering Line one should stay on its own row, Line two should stay on its own row, and Line three should stay on its own row on separate visible rows. The same screenshot also shows the fenced Swift code contents (let first = "alpha", let second = "beta", print(first + second)) and indented code lines A/B remaining multi-line instead of being collapsed into a single line.
  • Not tested / proof gaps: no physical iOS device was available; the after-fix visible proof was captured on the GitHub Actions iOS Simulator lane above.

@clawsweeper

clawsweeper Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge. Reviewed June 30, 2026, 6:48 AM ET / 10:48 UTC.

Summary
Adds a Swift chat Markdown rendering preprocessor that converts eligible single newlines to Markdown hard breaks and adds OpenClawKit preprocessor tests for prose and code-block cases.

PR surface: Other +140. Total +140 across 3 files.

Reproducibility: no. high-confidence current-main live reproduction was run in this read-only review. The issue is source-reproducible with medium confidence because current main preserves assistant newline bytes into ChatMarkdownRenderer, which then feeds cleaned text directly to Swift AttributedString(markdown:) without newline-specific handling, and the linked issue includes iOS screenshots.

Review metrics: 1 noteworthy metric.

  • Open fix candidates: 2 open PRs. Two branches target the same linked iOS rendering bug, so maintainers should choose one implementation rather than landing competing transforms.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #98028
Summary: This PR is one of two open candidate fixes for the canonical iOS assistant line-break rendering bug.

Members:

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

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🦞 diamond lobster ✨ media proof bonus
Patch quality: 🦐 gold shrimp
Result: needs maintainer review before merge.

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

Rank-up moves:

  • [P2] Fix the prose-to-fence boundary case and add a focused regression test.
  • [P2] Refresh the iOS simulator screenshot proof after the fix.

Risk before merge

Maintainer options:

  1. Fix the prose-to-fence boundary (recommended)
    Update the rendering preprocessor so a normal prose line before a fence still gets a visible hard break, add a regression test for that case, and refresh simulator proof before merge.
  2. Accept the remaining display gap
    Maintainers could intentionally accept that assistant prose adjacent to fenced code may still collapse, but that would leave part of the reported rendering bug visible.
  3. Choose the other candidate instead
    If maintainers prefer the sibling approach, pause this PR and use the chosen branch as the single canonical fix for the linked issue.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Fix the Swift chat Markdown rendering gap where a prose line immediately before a fenced code block still collapses; add focused ChatMarkdownPreprocessorTests coverage for that boundary, keep fence contents unchanged, and refresh iOS simulator proof.

Next step before merge

  • [P2] A focused repair can adjust the next-fence handling, add a targeted regression test, and refresh proof without changing provider, gateway, extraction, or broader chat behavior.

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

  • [P2] Preserve the break before fenced code — apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownPreprocessor.swift:254
Review details

Best possible solution:

Preserve prose newlines immediately before Markdown fence boundaries while leaving fence contents unchanged, refresh focused Swift coverage and iOS simulator proof, then land only one of the two open fix candidates.

Do we have a high-confidence way to reproduce the issue?

No high-confidence current-main live reproduction was run in this read-only review. The issue is source-reproducible with medium confidence because current main preserves assistant newline bytes into ChatMarkdownRenderer, which then feeds cleaned text directly to Swift AttributedString(markdown:) without newline-specific handling, and the linked issue includes iOS screenshots.

Is this the best way to solve the issue?

No, not as submitted: the renderer-level hard-break transform is the right layer, but the next-fence guard leaves a visible collapsed boundary in the provided simulator proof. The safer fix is to preserve prose newlines before fences while continuing to leave code-block contents unchanged.

Full review comments:

  • [P2] Preserve the break before fenced code — apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownPreprocessor.swift:254
    markdownForRendering skips hard-break insertion whenever the next line is a fence, so prose immediately before a fenced block still reaches Swift Markdown as a soft break. The provided iOS screenshot shows this as rowFenced code... on one visual line; add a hard break for the prose line while still leaving the fence and code contents unchanged.
    Confidence: 0.91

Overall correctness: patch is incorrect
Overall confidence: 0.89

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 738b2be4b49b.

Label changes

Label changes:

  • add merge-risk: 🚨 other: Merging as-is can leave a visible assistant formatting gap that the normal Swift and iOS build checks do not catch.
  • add rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • add status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (screenshot): The PR body links an inspected iOS simulator artifact for PR SHA 9253175 that shows the main prose-line improvement, though the same screenshot exposes the pre-fence gap captured in the review finding.
  • remove rating: 🐚 platinum hermit: Current PR rating is rating: 🦐 gold shrimp, so this older rating label is no longer current.
  • remove status: 👀 ready for maintainer look: Current PR status label is status: ⏳ waiting on author.

Label justifications:

  • P2: The PR targets a shipped iOS chat readability bug with limited blast radius and no data-loss, security, or availability impact.
  • merge-risk: 🚨 other: Merging as-is can leave a visible assistant formatting gap that the normal Swift and iOS build checks do not catch.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (screenshot): The PR body links an inspected iOS simulator artifact for PR SHA 9253175 that shows the main prose-line improvement, though the same screenshot exposes the pre-fence gap captured in the review finding.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body links an inspected iOS simulator artifact for PR SHA 9253175 that shows the main prose-line improvement, though the same screenshot exposes the pre-fence gap captured in the review finding.
  • proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence. The PR body links an inspected iOS simulator artifact for PR SHA 9253175 that shows the main prose-line improvement, though the same screenshot exposes the pre-fence gap captured in the review finding.
Evidence reviewed

PR surface:

Other +140. Total +140 across 3 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 3 141 1 +140
Total 3 141 1 +140

Acceptance criteria:

  • [P1] swift test --package-path apps/shared/OpenClawKit --filter ChatMarkdownPreprocessorTests.
  • [P1] swift test --package-path apps/shared/OpenClawKit --filter ChatEventTextTests.
  • [P1] iOS simulator screenshot proof showing prose lines and prose-before-fence boundaries render on separate visible rows.

What I checked:

Likely related people:

  • vincentkoc: GitHub path history shows commit cd7e3df replaced Textual StructuredText in ChatMarkdownRenderer with Swift AttributedString(markdown:), the rendering path this PR adjusts. (role: recent renderer contributor; confidence: high; commits: cd7e3df1eac9; files: apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownRenderer.swift)
  • jmcte: Recent iOS Dynamic Type work touched ChatMessageViews and nearby chat text call sites shortly before this bug report. (role: recent adjacent contributor; confidence: medium; commits: a54206f0f5d1; files: apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMessageViews.swift, apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatView.swift, apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatComposer.swift)
  • mbelinky: Earlier iOS chat cleanup modified ChatMarkdownPreprocessor and ChatMessageViews in the same shared native chat surface. (role: feature-area contributor; confidence: medium; commits: 9476dda9f617; files: apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownPreprocessor.swift, apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMessageViews.swift)
  • obviyus: Recent history on ChatMarkdownPreprocessor includes Markdown image flattening and review-feedback commits in the same preprocessing surface. (role: adjacent preprocessor contributor; confidence: medium; commits: 4bf902de5851, d25b493c7f79; files: apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownPreprocessor.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
@qingminglong

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. and removed 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. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jun 30, 2026
@joshavant

Copy link
Copy Markdown
Contributor

Thanks @qingminglong for working on this.

The issue has been fixed by #98304, merged as commit 47845e1. This PR was the closest sibling implementation: it used the same display-time hard-break approach and included simulator proof.

The final landed patch keeps that root-cause direction but adds the remaining edge-case handling for prose near fenced blocks, table detection versus pipe-containing prose, and stricter fence-close behavior, with focused regression tests and live iOS 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

merge-risk: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. P2 Normal backlog priority with limited blast radius. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. size: S status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants