function splitTableRow(line: string): string[] { let source = line.trim() if (source.startsWith('|')) source = source.slice(1) if (source.endsWith('|')) source = source.slice(0, -1) const cells: string[] = [] let cell = '' for (let index = 0; index < source.length; index += 1) { const char = source.charAt(index) if (char === '\\' && source.charAt(index + 1) === '|') { cell += '\\|' index += 1 continue } if (char === '|') { cells.push(cell.trim()) cell = '' continue } cell += char } cells.push(cell.trim()) return cells } function fenceMarker(line: string): string | null { const match = /^[ \t]{0,3}(`{3,}|~{3,})/u.exec(line) return match?.[1] ?? null } function isClosingFence(line: string, marker: string): boolean { const trimmed = line.trimStart() return trimmed.startsWith(marker.charAt(0).repeat(marker.length)) } function normalizedDelimiter(cell: string): string | null { const match = /^(:?)-+(:?)$/u.exec(cell.trim()) if (!match) return null return `${match[1]}---${match[2]}` } function normalizeDelimiterRow(previousLine: string | undefined, line: string): string { if (!previousLine?.includes('|') || !line.includes('|')) return line const headerCells = splitTableRow(previousLine) const delimiterCells = splitTableRow(line) if (delimiterCells.length < 2 || delimiterCells.length !== headerCells.length) return line const normalized = delimiterCells.map(normalizedDelimiter) if (normalized.some(cell => cell === null)) return line return `| ${normalized.join(' | ')} |` } /** * Some models emit compact Markdown table separators such as `|:--|:--|`. * GFM requires at least three dashes, so both BlockNote and remark otherwise * render the entire table as plain text. Repair only separator rows directly * below a same-width header and never touch fenced examples. */ export function normalizeMarkdownTableDelimiters(markdown: string): string { const lines = markdown.split(/\r?\n/u) let activeFence: string | null = null return lines.map((line, index) => { const marker = fenceMarker(line) if (activeFence) { if (marker && isClosingFence(line, activeFence)) activeFence = null return line } if (marker) { activeFence = marker return line } return normalizeDelimiterRow(lines[index - 1], line) }).join('\n') }