diff --git a/CLAUDE.md b/CLAUDE.md index 757a3f4..ed540a4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -168,9 +168,12 @@ UI thread redraws on its own tick (no channel; just the mutex). ## Gotchas -- `tui-markdown` is pinned `=0.3.5`: later versions use `ratatui-core` (0.30 - alpha types), incompatible with ratatui 0.29. Its gaps (no tables, literal - heading markers) are compensated in `src/markdown.rs`, not by upgrading. +- `tui-markdown` is pinned `=0.3.5`: 0.3.7+ moved to `ratatui-core` (0.30 alpha + types), incompatible with ratatui 0.29 — 0.3.6 is the last 0.29-compatible + release, but *no* version (through 0.3.8) enables pulldown-cmark's table + extension, so upgrading still wouldn't render tables. Its gaps (no tables, + literal heading markers) are compensated in `src/markdown.rs`, not by + upgrading. - `wezterm-term`/`wezterm-surface` are not on crates.io: pinned to a git rev of the wezterm monorepo (keep both revs identical). - The compact pane *dynamically frames* Claude Code's input box rather than diff --git a/src/markdown.rs b/src/markdown.rs index a846abb..d69e06f 100644 --- a/src/markdown.rs +++ b/src/markdown.rs @@ -3,9 +3,14 @@ //! tui-markdown 0.3.5 never enables pulldown-cmark's table extension, so GFM //! pipe tables fall through as raw paragraph text, and it keeps the literal //! `### ` markers on headings. This module splits table blocks out of the -//! text, renders them itself (box-drawing borders, width-fitted columns, -//! word-wrapped cells — matching Claude Code's own table style), routes the -//! rest through tui-markdown, and strips heading markers from its output. +//! text, renders them itself (box-drawing borders with a rule between every +//! row, width-fitted columns, word-wrapped cells), routes the rest through +//! tui-markdown, and strips heading markers from its output. +//! +//! Why not hand tables to a crate: the only tui-markdown still compatible with +//! ratatui 0.29 is 0.3.6 (0.3.7+ moved to ratatui-core / the 0.30 alpha), and +//! none of those versions enable pulldown-cmark's table extension — so an +//! upgrade still wouldn't render tables. This stays the owner of table layout. //! //! Re-rendered every frame on partial content: a table only renders as a //! table once its header + separator row have streamed in, so half-received @@ -278,7 +283,13 @@ fn render_table<'a>(rows: &[&str], width: u16, out: &mut Vec>) { out.push(border(&widths, '┌', '┬', '┐')); push_row(out, &header, &widths, &[Align::Center].repeat(cols)); out.push(border(&widths, '├', '┼', '┤')); - for row in &body { + // A rule between every body row, not just under the header: cells wrap to + // several lines each, so without internal separators consecutive rows run + // together into one unreadable block. + for (idx, row) in body.iter().enumerate() { + if idx > 0 { + out.push(border(&widths, '├', '┼', '┤')); + } push_row(out, row, &widths, &aligns); } out.push(border(&widths, '└', '┴', '┘')); @@ -396,6 +407,24 @@ mod tests { ); } + #[test] + fn body_rows_are_separated_by_a_rule() { + let md = "| A | B |\n|---|---|\n| 1 | 2 |\n| 3 | 4 |\n"; + let got = flat(&render(md, 40)); + assert_eq!( + got, + vec![ + "┌───┬───┐", + "│ A │ B │", + "├───┼───┤", + "│ 1 │ 2 │", + "├───┼───┤", + "│ 3 │ 4 │", + "└───┴───┘", + ] + ); + } + #[test] fn narrow_table_wraps_cells_within_width() { let md = "| Option | Notes |\n|---|---|\n| Delayed data | Far fewer licensing restrictions apply here |\n";