'use client' // Minimal, SAFE markdown -> React renderer. No dangerouslySetInnerHTML, no dependency. // Covers the subset LLMs emit: headings, bold, italic, inline code, fenced code, ul/ol, links. // (The Evidence Workspace renders citations in a separate pane, so links are rarely needed.) const INLINE_RE = /(`[^`]+`|\*\*[^*]+\*\*|\*[^*\s][^*]*\*|_[^_]+_|\[[^\]]+\]\([^)]+\))/g function renderInline(text: string, kp: string): React.ReactNode[] { const nodes: React.ReactNode[] = [] let last = 0 let idx = 0 INLINE_RE.lastIndex = 0 let m: RegExpExecArray | null while ((m = INLINE_RE.exec(text)) !== null) { if (m.index > last) nodes.push(text.slice(last, m.index)) const tok = m[0] const key = `${kp}-${idx++}` if (tok.startsWith('`')) { nodes.push( {tok.slice(1, -1)} , ) } else if (tok.startsWith('**')) { nodes.push( {tok.slice(2, -2)} , ) } else if (tok.startsWith('*') || tok.startsWith('_')) { nodes.push({tok.slice(1, -1)}) } else { const mm = /^\[([^\]]+)\]\(([^)]+)\)$/.exec(tok) if (mm && /^https?:\/\//i.test(mm[2])) { nodes.push( {mm[1]} , ) } else { nodes.push(mm ? mm[1] : tok) } } last = m.index + tok.length } if (last < text.length) nodes.push(text.slice(last)) return nodes } function Heading({ level, kp, text }: { level: number; kp: string; text: string }) { const children = renderInline(text, kp) if (level <= 1) return

{children}

if (level === 2) return

{children}

return
{children}
} const UL_RE = /^\s*[-*]\s+/ const OL_RE = /^\s*\d+\.\s+/ const H_RE = /^(#{1,6})\s+(.*)$/ export function Markdown({ content }: { content: string }) { const lines = (content || '').replace(/\r\n/g, '\n').split('\n') const blocks: React.ReactNode[] = [] let i = 0 while (i < lines.length) { const line = lines[i] const key = `b${blocks.length}` // unique per pushed block (blocks.length is the next index) if (line.trim().startsWith('```')) { const buf: string[] = [] i++ while (i < lines.length && !lines[i].trim().startsWith('```')) { buf.push(lines[i]) i++ } i++ blocks.push(
          {buf.join('\n')}
        
, ) continue } if (line.trim() === '') { i++ continue } const h = H_RE.exec(line) if (h) { blocks.push() i++ continue } if (UL_RE.test(line)) { const items: string[] = [] while (i < lines.length && UL_RE.test(lines[i])) { items.push(lines[i].replace(UL_RE, '')) i++ } blocks.push( , ) continue } if (OL_RE.test(line)) { const items: string[] = [] while (i < lines.length && OL_RE.test(lines[i])) { items.push(lines[i].replace(OL_RE, '')) i++ } blocks.push(
    {items.map((it, k) => (
  1. {renderInline(it, `${key}-${k}`)}
  2. ))}
, ) continue } const para: string[] = [] while ( i < lines.length && lines[i].trim() !== '' && !H_RE.test(lines[i]) && !UL_RE.test(lines[i]) && !OL_RE.test(lines[i]) && !lines[i].trim().startsWith('```') ) { para.push(lines[i]) i++ } blocks.push(

{renderInline(para.join(' '), key)}

, ) } return
{blocks}
}