From 9eabc24e245f253ccb073128e6344d09cb806580 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Tue, 28 Apr 2026 13:56:39 -0500 Subject: [PATCH] fix(tui): visually distinguish markdown table rows from prose (#15534) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tables rendered through `` had no separator and no header weight, so they read as a paragraph with extra whitespace. This adds two tiny, border-free changes that survive Ink's grapheme-approximate column widths better than a full outline: * Bold the header row, keeping the existing amber colour. * Insert a dim `─`-dashed rule between the header and body rows. We deliberately stay away from a full outline — column widths are measured via `stripInlineMarkup(...).length`, which is grapheme-aware but still off by a cell on East Asian wide characters and emoji-mid- cell strings. A header rule plus the existing 2-space column gap gives the visual hierarchy the issue asks for without amplifying that inaccuracy into a misaligned border. Validation: `npm run type-check` clean, `npm test --run` 389/389. --- ui-tui/src/components/markdown.tsx | 33 ++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/ui-tui/src/components/markdown.tsx b/ui-tui/src/components/markdown.tsx index d3b6710b9e..7afae3beb6 100644 --- a/ui-tui/src/components/markdown.tsx +++ b/ui-tui/src/components/markdown.tsx @@ -1,5 +1,5 @@ import { Box, Link, Text } from '@hermes/ink' -import { memo, type ReactNode, useMemo } from 'react' +import { Fragment, memo, type ReactNode, useMemo } from 'react' import { ensureEmojiPresentation } from '../lib/emoji.js' import { highlightLine, isHighlightable } from '../lib/syntax.js' @@ -97,18 +97,33 @@ export const stripInlineMarkup = (v: string) => const renderTable = (k: number, rows: string[][], t: Theme) => { const widths = rows[0]!.map((_, ci) => Math.max(...rows.map(r => stripInlineMarkup(r[ci] ?? '').length))) + // Thin divider under the header. Without it tables look like prose + // with extra spacing because the header is just amber-coloured text + // (#15534). We avoid full borders on purpose — column widths are + // grapheme-approximate so a real outline often misaligns; one dim + // dashed rule under row 0 plus tab-style column gaps reads cleanly + // on every terminal we tested. + const sep = widths.map(w => '─'.repeat(Math.max(1, w))).join(' ') + return ( {rows.map((row, ri) => ( - - {widths.map((w, ci) => ( - - - {' '.repeat(Math.max(0, w - stripInlineMarkup(row[ci] ?? '').length))} - {ci < widths.length - 1 ? ' ' : ''} + + + {widths.map((w, ci) => ( + + + {' '.repeat(Math.max(0, w - stripInlineMarkup(row[ci] ?? '').length))} + {ci < widths.length - 1 ? ' ' : ''} + + ))} + + {ri === 0 && rows.length > 1 ? ( + + {sep} - ))} - + ) : null} + ))} )