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];