From d8ab326b73a1aadf5ed6a6fc29afce25530fa904 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Tue, 14 Apr 2026 21:22:20 +0000 Subject: [PATCH] fix(renderer): fix two remaining renderMd issues found during browser QA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. ** inside was corrupted** — the outer bold/italic pass at line 480 ran after the outer backtick→ pass at line 457, causing esc() to corrupt tags into <code> inside . Fix: add _ob_stash to protect tags from the outer bold/italic pass. 2. **Table cells with [label](url) produced double tags** — the outer [label](url) pass ran BEFORE the table regex, converting links to tags in the raw table source. Then inlineMd() processed those tags again and autolink re-linked the URL inside href="...". Fix: moved the outer link pass to AFTER the table pass so table cells get their links from inlineMd() only, which has its own _link_stash protection. --- static/ui.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/static/ui.js b/static/ui.js index 33cc417..00926b2 100644 --- a/static/ui.js +++ b/static/ui.js @@ -477,9 +477,14 @@ function renderMd(raw){ t=t.replace(/<\/?[a-z][^>]*>/gi,tag=>SAFE_INLINE.test(tag)?tag:esc(tag)); return t; } + // Stash tags from the backtick pass above so the outer bold/italic + // regexes don't esc() their content (e.g. **`code`** → code) + const _ob_stash=[]; + s=s.replace(/([^<]*<\/code>)/g,m=>{_ob_stash.push(m);return `\x00O${_ob_stash.length-1}\x00`;}); s=s.replace(/\*\*\*(.+?)\*\*\*/g,(_,t)=>`${esc(t)}`); s=s.replace(/\*\*(.+?)\*\*/g,(_,t)=>`${esc(t)}`); s=s.replace(/\*([^*\n]+)\*/g,(_,t)=>`${esc(t)}`); + s=s.replace(/\x00O(\d+)\x00/g,(_,i)=>_ob_stash[+i]); s=s.replace(/^### (.+)$/gm,(_,t)=>`

${inlineMd(t)}

`).replace(/^## (.+)$/gm,(_,t)=>`

${inlineMd(t)}

`).replace(/^# (.+)$/gm,(_,t)=>`

${inlineMd(t)}

`); s=s.replace(/^---+$/gm,'
'); s=s.replace(/^> (.+)$/gm,(_,t)=>`
${inlineMd(t)}
`); @@ -504,12 +509,9 @@ function renderMd(raw){ } return html+''; }); - // Stash existing
tags so the autolink pass below does not re-link their href= URLs - const _a_stash=[]; - s=s.replace(/(]*>[\s\S]*?<\/a>)/g,m=>{_a_stash.push(m);return `\x00A${_a_stash.length-1}\x00`;}); - s=s.replace(/\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g,(_,label,url)=>`${esc(label)}`); - s=s.replace(/\x00A(\d+)\x00/g,(_,i)=>_a_stash[+i]); // Tables: | col | col | header row followed by | --- | --- | separator then data rows + // NOTE: table pass runs BEFORE outer link pass so [label](url) in table cells + // is handled by inlineMd() only — prevents double-linking. s=s.replace(/((?:^\|.+\|\n?)+)/gm,block=>{ const rows=block.trim().split('\n').filter(r=>r.trim()); if(rows.length<2)return block; @@ -521,6 +523,13 @@ function renderMd(raw){ const body=rows.slice(2).map(r=>`${parseRow(r)}`).join(''); return `${header}${body}
`; }); + // Outer link pass for labeled links in plain paragraphs (outside table cells). + // Runs AFTER the table pass so table cells are processed by inlineMd() only. + // Stash existing tags first to avoid re-linking already-linked URLs. + const _a_stash=[]; + s=s.replace(/(]*>[\s\S]*?<\/a>)/g,m=>{_a_stash.push(m);return `\x00A${_a_stash.length-1}\x00`;}); + s=s.replace(/\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g,(_,label,url)=>`${esc(label)}`); + s=s.replace(/\x00A(\d+)\x00/g,(_,i)=>_a_stash[+i]); // Escape any remaining HTML tags that are NOT from our own markdown output. // Our pipeline only emits: ,,,
,,
    ,
      ,
    1. , // ,,,,
      ,,
      ,
      ,

      ,
      ,,