From 8c6abcb41feb8f1c2f88df351980ae82d744849b Mon Sep 17 00:00:00 2001 From: Geoffrey Marc <7069588+outofrange-consulting@users.noreply.github.com> Date: Mon, 1 Jun 2026 09:57:06 +0200 Subject: [PATCH] fix(app): render bold/italic/strikethrough and line breaks via UITextView span on iOS (#1254) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(app): render bold/italic/strikethrough via UITextView span on iOS strong/em/s had no custom render rule and fell back to react-native-markdown-display's defaults, which wrap children in a plain RN . On iOS the paragraph/textgroup are native UITextViews (markdown-text.ios.tsx); a plain nested inside is not hoisted into a UITextViewChild, so its content rendered invisibly — bold/italic/ strikethrough silently disappeared (regression from #1153, v0.1.84). Route strong/em/s through MarkdownInheritedText -> MarkdownTextSpan, the same path text/textgroup already use, so the styled run composes as a UITextViewChild on iOS and stays visible + selectable. Web/Android use a plain for MarkdownTextSpan, so behavior there is unchanged. Fixes #1253 Co-Authored-By: Claude Opus 4.8 (1M context) * fix(app): render hard/soft line breaks via UITextView span on iOS hardbreak/softbreak had no custom rule, so react-native-markdown-display emitted a plain RN {"\n"}. Inside the paragraph UITextView that plain is dropped (same root cause as strong/em/s/link): on iOS hard line breaks vanished, and a softbreak between words joined them ("one\ntwo" -> "onetwo") — the "adjacent words dropped" symptom from the original report. Route both through MarkdownTextSpan so the newline composes as a UITextViewChild on iOS; web/Android keep the same "\n". Refs #1253 Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Geoffrey Co-authored-by: Claude Opus 4.8 (1M context) --- packages/app/src/components/message.tsx | 61 +++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/packages/app/src/components/message.tsx b/packages/app/src/components/message.tsx index bf53770c7..f78a43808 100644 --- a/packages/app/src/components/message.tsx +++ b/packages/app/src/components/message.tsx @@ -1622,6 +1622,67 @@ export const AssistantMessage = memo(function AssistantMessage({ {children} ), + // strong/em/s have no custom rule in react-native-markdown-display's + // defaults beyond wrapping children in a plain RN . On iOS the + // paragraph/textgroup are native UITextViews (see markdown-text.ios.tsx), + // and a plain nested inside one is not hoisted into a + // UITextViewChild, so its content renders invisibly. Route these inline + // marks through MarkdownTextSpan (same path as text/textgroup) so the + // styled content composes and stays visible + selectable on iOS. + strong: ( + node: ASTNode, + children: ReactNode[], + _parent: ASTNode[], + styles: MarkdownStyles, + inheritedStyles: TextStyle = {}, + ) => ( + + {children} + + ), + em: ( + node: ASTNode, + children: ReactNode[], + _parent: ASTNode[], + styles: MarkdownStyles, + inheritedStyles: TextStyle = {}, + ) => ( + + {children} + + ), + s: ( + node: ASTNode, + children: ReactNode[], + _parent: ASTNode[], + styles: MarkdownStyles, + inheritedStyles: TextStyle = {}, + ) => ( + + {children} + + ), + // hardbreak/softbreak fall back to react-native-markdown-display's + // default, a plain RN {"\n"}. Inside the paragraph UITextView that + // plain is not hoisted into a UITextViewChild and is dropped (same + // root cause as strong/em/s) — so on iOS a hard line break vanished, and + // a softbreak between words jammed them together ("one\ntwo" -> "onetwo"). + // Emit the break through MarkdownTextSpan so it composes on iOS; web and + // Android keep the same "\n" they rendered before. + hardbreak: (node: ASTNode) => {"\n"}, + softbreak: (node: ASTNode) => {"\n"}, code_block: ( node: ASTNode, _children: ReactNode[],