diff --git a/apps/mobile/src/lib/markdownDocument.ts b/apps/mobile/src/lib/markdownDocument.ts
index af8fe2d02a15f..d79003da234cd 100644
--- a/apps/mobile/src/lib/markdownDocument.ts
+++ b/apps/mobile/src/lib/markdownDocument.ts
@@ -168,9 +168,14 @@ function markdownHtmlTags(content: string): RegExpExecArray[] {
codeRanges.push({ start: opening.index, end: closing.index + closing[0].length });
index = closingIndex;
}
- return tags.filter(
- (tag) => !codeRanges.some((range) => tag.index >= range.start && tag.index < range.end),
- );
+ let codeRangeIndex = 0;
+ return tags.filter((tag) => {
+ while (codeRanges[codeRangeIndex] && tag.index >= codeRanges[codeRangeIndex]!.end) {
+ codeRangeIndex += 1;
+ }
+ const range = codeRanges[codeRangeIndex];
+ return !range || tag.index < range.start;
+ });
}
/**
diff --git a/apps/mobile/src/lib/markdownHtml.test.ts b/apps/mobile/src/lib/markdownHtml.test.ts
index e27cf812ed436..1a600064fc539 100644
--- a/apps/mobile/src/lib/markdownHtml.test.ts
+++ b/apps/mobile/src/lib/markdownHtml.test.ts
@@ -106,6 +106,28 @@ describe("HTML in native Markdown", () => {
});
});
+it("keeps code literal across many ranges while converting HTML between and after them", () => {
+ const blocks = Array.from(
+ { length: 100 },
+ (_, index) => `\`
\`
`,
+ );
+ const expected = Array.from(
+ { length: 100 },
+ (_, index) => `\`
\` ![]()`,
+ );
+ expect(markdownHtmlContent(blocks.join("\n") + "
after")).toBe(
+ expected.join("\n") + " \nafter",
+ );
+});
+
+it("keeps tags after an unclosed fence literal even when later inline ranges overlap it", () => {
+ const content =
+ 'before
\n```html\n
\n`
`\n
';
+ expect(markdownHtmlContent(content)).toBe(
+ '\n\nbefore\n\n\n```html\n
\n`
`\n
',
+ );
+});
+
describe("file embeds in native thread and file Markdown", () => {
const input = {
environmentId: EnvironmentId.make("environment-1"),