From 731378e29fa88d279dd110cdbcd8a69f2d7abbaf Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 25 Aug 2026 00:36:31 +0100 Subject: [PATCH] fix: more escapes --- .../MarkdownAnswer.codeSpans.test.ts | 28 +++++++++++++++++++ frontend/src/conversation/MarkdownAnswer.tsx | 10 ++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/frontend/src/conversation/MarkdownAnswer.codeSpans.test.ts b/frontend/src/conversation/MarkdownAnswer.codeSpans.test.ts index dc3e0be9..befd3305 100644 --- a/frontend/src/conversation/MarkdownAnswer.codeSpans.test.ts +++ b/frontend/src/conversation/MarkdownAnswer.codeSpans.test.ts @@ -36,6 +36,34 @@ describe('citation rewriting spares code', () => { expect(text('inline ```code[0]``` end')).toBe('inline ```code[0]``` end'); }); + it('leaves a triple-backtick span that starts a line alone', () => { + // A backtick fence's info string cannot contain backticks, so this opens a + // code span, not an unclosed fence that swallows the rest of the answer. + expect(text('```code[0]``` and see [1]')).toBe( + '```code[0]``` and see [1](#cite-1)', + ); + }); + + it('closes an inline span only on a run of its own length', () => { + // The `` run does not close the ` span, so `b[1]` is still code. + const source = 'Use `a`` b[1] more` end'; + expect(text(source)).toBe(source); + }); + + it('recognises a fence closed on a CRLF line', () => { + expect(text('```js\r\nconst a = arr[0];\r\n```\r\nSee [1].')).toBe( + '```js\r\nconst a = arr[0];\r\n```\r\nSee [1](#cite-1).', + ); + }); + + it('does not carry an inline span across a blank line', () => { + // The blank line ends the paragraph, so the backticks never pair and + // `bar[1]` is prose — which is how remark parses it too. + expect(text('a `foo\n\nbar[1]` end')).toBe( + 'a `foo\n\nbar[1](#cite-1)` end', + ); + }); + it('leaves an inline span that wraps a line alone', () => { expect(text('use `arr[0]\nnext` here')).toBe('use `arr[0]\nnext` here'); }); diff --git a/frontend/src/conversation/MarkdownAnswer.tsx b/frontend/src/conversation/MarkdownAnswer.tsx index 479444a8..b85e58fb 100644 --- a/frontend/src/conversation/MarkdownAnswer.tsx +++ b/frontend/src/conversation/MarkdownAnswer.tsx @@ -26,11 +26,13 @@ import { // ```` fence closes only on ```` and nested fences stay masked. The // unterminated alternatives keep an open span matched too: answers stream in, // so a fence is open for most of its life and needs protecting the whole time, -// not just once the closing fence arrives. Four-space indented blocks are -// deliberately not masked — list continuation lines are indented the same way, -// and masking those would drop real citations out of nested lists. +// not just once the closing fence arrives. A backtick fence's info string may +// not itself contain backticks, so a line that opens with an inline span stays +// an inline span. Four-space indented blocks are deliberately not masked — +// list continuation lines are indented the same way, and masking those would +// drop real citations out of nested lists. const CODE_SPAN = - /(?