From 0ed06115253d459186375611ff489ff176664119 Mon Sep 17 00:00:00 2001 From: Chris Deeming Date: Thu, 13 Aug 2026 16:09:43 +0100 Subject: [PATCH 1/2] fix(mobile): extend blockquotes across wrapped lines --- .../src/nativeMarkdownText.ts | 1 + .../mobile/src/lib/nativeMarkdownText.test.ts | 35 ++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/apps/mobile/modules/t3-markdown-text/src/nativeMarkdownText.ts b/apps/mobile/modules/t3-markdown-text/src/nativeMarkdownText.ts index dc84755cbbd..719070f3dcd 100644 --- a/apps/mobile/modules/t3-markdown-text/src/nativeMarkdownText.ts +++ b/apps/mobile/modules/t3-markdown-text/src/nativeMarkdownText.ts @@ -661,6 +661,7 @@ function appendDocumentBlock( function containsRichBlock(node: MarkdownNode): boolean { if ( node.type === "code_block" || + node.type === "blockquote" || node.type === "table" || node.type === "image" || node.type === "horizontal_rule" || diff --git a/apps/mobile/src/lib/nativeMarkdownText.test.ts b/apps/mobile/src/lib/nativeMarkdownText.test.ts index 6e41f2243a9..e63752b9a15 100644 --- a/apps/mobile/src/lib/nativeMarkdownText.test.ts +++ b/apps/mobile/src/lib/nativeMarkdownText.test.ts @@ -328,7 +328,7 @@ describe("nativeMarkdownDocumentRuns", () => { ]); }); - it("includes quotes and fenced code in the same selectable string", () => { + it("preserves quotes and fenced code in document runs", () => { const node: MarkdownNode = { type: "document", children: [ @@ -414,6 +414,39 @@ describe("nativeMarkdownListItemBlocks", () => { }); describe("nativeMarkdownDocumentChunks", () => { + it("renders plain blockquotes as rich blocks so their marker spans wrapped lines", () => { + const blockquote: MarkdownNode = { + type: "blockquote", + beg: 0, + end: 120, + children: [ + { + type: "paragraph", + children: [ + { + type: "text", + content: + "Persistent random per-result keys are the strongest design, even when this text wraps.", + }, + ], + }, + ], + }; + + expect( + nativeMarkdownDocumentChunks({ + type: "document", + children: [blockquote], + }), + ).toEqual([ + { + kind: "rich", + key: "rich:blockquote:0:120", + node: blockquote, + }, + ]); + }); + it("keeps headings and plain lists in one selectable document", () => { const document: MarkdownNode = { type: "document", From d0a24be2958aa8bd480c0f77b999a33058c2b135 Mon Sep 17 00:00:00 2001 From: Chris Deeming Date: Thu, 13 Aug 2026 16:39:09 +0100 Subject: [PATCH 2/2] fix(mobile): preserve skills in rich markdown blocks --- .../src/NativeMarkdownBlock.ios.tsx | 31 ++++++++++++++----- .../src/SelectableMarkdownText.ios.tsx | 1 + .../mobile/src/lib/nativeMarkdownText.test.ts | 19 ++++++++++++ 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.ios.tsx b/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.ios.tsx index e6a045b3cd9..5fbe6d4dff4 100644 --- a/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.ios.tsx +++ b/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.ios.tsx @@ -4,16 +4,13 @@ import type { MarkdownNode } from "react-native-nitro-markdown/headless"; import { CopyTextButton } from "./CopyTextButton"; import { MarkdownTextPrimitive } from "./MarkdownTextPrimitive"; -import { - nativeMarkdownDocumentRuns, - nativeMarkdownListItemBlocks, - nativeMarkdownTextRuns, -} from "./nativeMarkdownText"; +import { nativeMarkdownDocumentRuns, nativeMarkdownListItemBlocks } from "./nativeMarkdownText"; import { NativeMarkdownSelectableText } from "./NativeMarkdownSelectableText.ios"; import type { MarkdownCodeHighlighter, MarkdownHighlightedToken, NativeMarkdownTextStyle, + SelectableMarkdownSkill, } from "./SelectableMarkdownText.types"; type HighlightedCode = ReadonlyArray>; @@ -48,12 +45,13 @@ function documentFor(node: MarkdownNode): MarkdownNode { function SelectableNode(props: { readonly node: MarkdownNode; + readonly skills: ReadonlyArray; readonly textStyle: NativeMarkdownTextStyle; readonly onLinkPress?: (href: string) => void; }) { return ( @@ -322,6 +320,7 @@ function collectTableRows(node: MarkdownNode): MarkdownNode[] { function NativeTable(props: { readonly node: MarkdownNode; + readonly skills: ReadonlyArray; readonly textStyle: NativeMarkdownTextStyle; readonly onLinkPress?: (href: string) => void; }) { @@ -359,7 +358,7 @@ function NativeTable(props: { }} > + runs={nativeMarkdownDocumentRuns(documentFor(cell), props.skills).map((run) => rowIndex === 0 || cell.isHeader ? { ...run, bold: true } : run, )} textStyle={props.textStyle} @@ -376,6 +375,7 @@ function NativeTable(props: { function NativeMarkdownImage(props: { readonly node: MarkdownNode; + readonly skills: ReadonlyArray; readonly textStyle: NativeMarkdownTextStyle; readonly onLinkPress?: (href: string) => void; }) { @@ -384,6 +384,7 @@ function NativeMarkdownImage(props: { return ( @@ -445,6 +446,7 @@ function inlineGroups(nodes: ReadonlyArray): MarkdownNode[] { function NativeMixedParagraph(props: { readonly node: MarkdownNode; + readonly skills: ReadonlyArray; readonly textStyle: NativeMarkdownTextStyle; readonly onLinkPress?: (href: string) => void; }) { @@ -455,6 +457,7 @@ function NativeMixedParagraph(props: { @@ -462,6 +465,7 @@ function NativeMixedParagraph(props: { @@ -473,6 +477,7 @@ function NativeMixedParagraph(props: { function NativeList(props: { readonly node: MarkdownNode; + readonly skills: ReadonlyArray; readonly textStyle: NativeMarkdownTextStyle; readonly highlightCode: MarkdownCodeHighlighter; readonly onLinkPress?: (href: string) => void; @@ -534,6 +539,7 @@ function NativeList(props: { ; readonly textStyle: NativeMarkdownTextStyle; readonly highlightCode: MarkdownCodeHighlighter; readonly onLinkPress?: (href: string) => void; @@ -566,6 +573,7 @@ export function NativeMarkdownBlock(props: { @@ -595,6 +604,7 @@ export function NativeMarkdownBlock(props: { return ( @@ -624,6 +634,7 @@ export function NativeMarkdownBlock(props: { child.type === "image") ? ( ) : ( @@ -673,6 +687,7 @@ export function NativeMarkdownBlock(props: { > @@ -690,6 +705,7 @@ export function NativeMarkdownBlock(props: { diff --git a/apps/mobile/modules/t3-markdown-text/src/SelectableMarkdownText.ios.tsx b/apps/mobile/modules/t3-markdown-text/src/SelectableMarkdownText.ios.tsx index 56321ba01ad..7860ff592a6 100644 --- a/apps/mobile/modules/t3-markdown-text/src/SelectableMarkdownText.ios.tsx +++ b/apps/mobile/modules/t3-markdown-text/src/SelectableMarkdownText.ios.tsx @@ -69,6 +69,7 @@ export function SelectableMarkdownText({ chunk.kind === "rich" ? ( { ]); }); + it("decorates known skill references inside blockquotes", () => { + const node: MarkdownNode = { + type: "blockquote", + children: [ + { + type: "paragraph", + children: [{ type: "text", content: "Use $ui for this." }], + }, + ], + }; + + expect(nativeMarkdownDocumentRuns(node, [{ name: "ui", displayName: "UI" }])).toContainEqual({ + text: "$ui", + role: "body", + skillName: "ui", + skillLabel: "UI", + }); + }); + it("leaves unknown skill-like text unchanged", () => { const node: MarkdownNode = { type: "document",