fix(desktop): skip lone-tilde escaping on directive lines; align prose tests with the unknown-tag escape

This commit is contained in:
Hermes Agent
2026-09-25 17:19:28 -05:00
committed by brooklyn!
parent 25381c17d8
commit a00fdda6c5
3 changed files with 31 additions and 3 deletions

View File

@@ -58,8 +58,11 @@ describe('preprocessMarkdown / directive lines', () => {
})
it('does not touch prose that merely contains ::', () => {
// The `::` itself is never structure, but `<int>` is an unknown html-like
// token: escaping it to entities is the #53953 behavior (the renderer
// would otherwise swallow it), and it paints identically as literal text.
const text = 'Use std::vector<int> for *speed*.'
expect(preprocessMarkdown(text)).toBe(text)
expect(preprocessMarkdown(text)).toBe('Use std::vector&lt;int&gt; for *speed*.')
})
})

View File

@@ -19,6 +19,12 @@ describe('reasoning blocks in streamed markdown', () => {
expect(preprocessMarkdown('Answer.\n<div')).toBe('Answer.\n<div')
const quoted = 'O texto acima explica o formato do bloco <thinking> sem nunca fechá-lo'
expect(preprocessMarkdown(quoted)).toBe(quoted)
// The mid-sentence mention is escaped to entities by the #53953 unknown-tag
// pass — the renderer would otherwise read it as an unclosed tag — so the
// round-trip differs from the source by exactly that escape, and the
// escaped form paints identically as literal text.
expect(preprocessMarkdown(quoted)).toBe(
'O texto acima explica o formato do bloco &lt;thinking&gt; sem nunca fechá-lo'
)
})
})

View File

@@ -102,6 +102,10 @@ const URL_LIKE_SPLIT_RE = /(<https?:\/\/[^>\s]+>|https?:\/\/[^\s<>"'`*]+[^\s<>"'
// literal tilde, so CJK ranges (`1~10`) and approximation prefixes (`~¥0.089`)
// no longer pair up into a GFM strikethrough span.
const LONE_TILDE_RE = /(?<![\\~])~(?!~)/g
// Same shape as DIRECTIVE_LINE_RE, anchored to a single line: escapeLoneTildes
// tests one line at a time and must not carry the shared regex's `g` flag
// (stateful lastIndex would skip every other directive line).
const DIRECTIVE_LINE_ONLY_RE = /^[ \t]*::[a-z][a-z0-9-]{0,63}\{[^{}\n]{0,1024}\}[ \t]*$/
// HTML-shaped prose tokens (`<tool_call>`, `<observation>`...) that are NOT
// real inline elements get swallowed by the HTML-aware renderer (parse5 sees
// an unclosed tag and consumes the rest of the message). Match unknown tag-like
@@ -313,7 +317,22 @@ function autoLinkRawUrls(text: string): string {
function escapeLoneTildes(text: string): string {
return text
.split(URL_LIKE_SPLIT_RE)
.map(part => (/^<?https?:\/\//i.test(part) ? part : part.replace(LONE_TILDE_RE, '\\~')))
.map(part => {
if (/^<?https?:\/\//i.test(part)) {
return part
}
// Directive lines are shielded verbatim further down the pipeline:
// shieldDirectiveLines backslash-escapes every inline metachar (`~`
// included) so the card value arrives as ONE text node. Escaping a `~`
// here would leave `\\~` after the shield doubles the backslash, and the
// parser then emits the stray `\` into the directive's rendered value
// (#50871 follow-up: `brief="Sync ~/notes to ~/backup nightly"`).
return part
.split('\n')
.map(line => (DIRECTIVE_LINE_ONLY_RE.test(line) ? line : line.replace(LONE_TILDE_RE, '\\~')))
.join('\n')
})
.join('')
}