From 40bccbb6a73771a9cef9c0cf083e49d035c91f04 Mon Sep 17 00:00:00 2001 From: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com> Date: Tue, 29 Sep 2026 11:40:57 -0700 Subject: [PATCH 1/2] fix(mobile): text selection now spans paragraphs inside a blockquote Motivation: each paragraph in a blockquote rendered in its own native text view. iOS selection cannot cross text views, so dragging a selection handle stopped at the end of the first quoted paragraph. Approach: the blockquote branch of NativeMarkdownBlock now reuses nativeMarkdownDocumentChunks on the quote's children. Consecutive plain children (paragraphs, plain lists) share one selectable text view; code, tables, images and nested quotes stay separate rich blocks. The quote bar remains a border on the parent View, so it still spans wrapped lines. Side effect: paragraphs merged into one text view use the same paragraph spacing as top-level markdown, so quoted paragraph gaps are slightly larger than the previous 6px. Validation: - pnpm exec vp test run src/lib/nativeMarkdownText.test.ts (apps/mobile): 60 passed. The added test covers the chunk grouping the fix relies on; it exercises an unchanged helper and would also pass before this change. - vp fmt, vp lint on the changed files and tsc --noEmit in apps/mobile reported no errors. - Not run: iOS simulator or Android. The rendering change itself has no automated coverage, and cross-paragraph selection is unverified on a device. Report: https://github.com/pingdotgg/t3code/issues/14288 Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com> Assisted-by: claude-sonnet-5-5 (via Claude Code) --- .../src/NativeMarkdownBlock.tsx | 40 +++++++++++++------ .../mobile/src/lib/nativeMarkdownText.test.ts | 19 +++++++++ 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.tsx b/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.tsx index b57a182dbc56c..fd3f85ba3a744 100644 --- a/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.tsx +++ b/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.tsx @@ -5,6 +5,7 @@ import type { MarkdownNode } from "react-native-nitro-markdown/headless"; import { CopyTextButton } from "./CopyTextButton"; import { MarkdownTextPrimitive } from "./MarkdownTextPrimitive"; import { + nativeMarkdownDocumentChunks, nativeMarkdownDocumentRuns, nativeMarkdownListItemBlocks, nativeMarkdownNodePosition, @@ -567,18 +568,33 @@ export function NativeMarkdownBlock(props: { gap: 6, }} > - {(props.node.children ?? []).map((child, index) => ( - - ))} + {/* Consecutive plain children share one text view so selection can + cross paragraphs; iOS selection cannot span separate text views. */} + {nativeMarkdownDocumentChunks({ + type: "document", + children: props.node.children ?? [], + }).map((chunk) => + chunk.kind === "selectable" ? ( + + ) : ( + + ), + )} ); case "list": diff --git a/apps/mobile/src/lib/nativeMarkdownText.test.ts b/apps/mobile/src/lib/nativeMarkdownText.test.ts index d934a306aa01d..19c57f4e6c223 100644 --- a/apps/mobile/src/lib/nativeMarkdownText.test.ts +++ b/apps/mobile/src/lib/nativeMarkdownText.test.ts @@ -669,6 +669,25 @@ describe("nativeMarkdownDocumentChunks", () => { ]); }); + it("groups plain blockquote children into one selectable chunk so selection can span paragraphs", () => { + const paragraph = (content: string): MarkdownNode => ({ + type: "paragraph", + children: [{ type: "text", content }], + }); + const code: MarkdownNode = { type: "code_block", content: "const a = 1;" }; + + const chunks = nativeMarkdownDocumentChunks({ + type: "document", + children: [paragraph("First"), paragraph("Second"), code, paragraph("Third")], + }); + + expect(chunks.map((chunk) => [chunk.kind, chunk.node.children?.length ?? 0])).toEqual([ + ["selectable", 2], + ["rich", 0], + ["selectable", 1], + ]); + }); + it("keeps headings and plain lists in one selectable document", () => { const document: MarkdownNode = { type: "document", From 2a443b899d0d6192d7b9bbdbf3ddae81ff608817 Mon Sep 17 00:00:00 2001 From: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com> Date: Tue, 29 Sep 2026 13:38:54 -0700 Subject: [PATCH 2/2] fix(mobile): keep list depth for lists inside blockquotes in selectable chunks Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com> --- .../modules/t3-markdown-text/src/NativeMarkdownBlock.tsx | 4 +++- .../mobile/modules/t3-markdown-text/src/nativeMarkdownText.ts | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.tsx b/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.tsx index fd3f85ba3a744..dd3926e4ed339 100644 --- a/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.tsx +++ b/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.tsx @@ -58,10 +58,11 @@ function SelectableNode(props: { readonly skills: ReadonlyArray; readonly textStyle: NativeMarkdownTextStyle; readonly onLinkPress?: (href: string) => void; + readonly depth?: number; }) { return ( @@ -581,6 +582,7 @@ export function NativeMarkdownBlock(props: { skills={props.skills} textStyle={props.textStyle} onLinkPress={props.onLinkPress} + depth={depth} /> ) : ( = [], + depth = 0, ): ReadonlyArray { - const runs = appendDocumentBlock([], node); + const runs = appendDocumentBlock([], node, depth); while (runs.length > 0) { const lastIndex = runs.length - 1; const last = runs[lastIndex];