Skip to content

feat(tui): render fenced code on a surface, sized to the frame - #183

Merged
BunsDev merged 2 commits into
mainfrom
feat/code-block-surface
Sep 15, 2026
Merged

BunsDev merged 2 commits into
mainfrom
feat/code-block-surface

Conversation

@BunsDev

@BunsDev BunsDev commented Sep 15, 2026

Copy link
Copy Markdown
Member

What

Fenced code in the transcript was drawn as a fixed 50-dash rule plus a yellow │ gutter, with the raw source line pushed verbatim (crates/tui/src/messages/markdown.rs:23). Three problems followed.

The block had no edges. Code inherited the app background, so a fence bled into the prose around it and read as indented text rather than as a block.

The rules ignored the frame. Hardcoded 50 dashes regardless of terminal width, so they under- or overshot on every terminal that wasn't exactly that wide.

Long lines were clipped silently. render_markdown already receives the frame width and the code path ignored it, so ratatui cut the line at the buffer edge with no marker. A truncated line was indistinguishable from a complete one — which matters most for exactly the content a reader is most likely to copy.

After

  ┌─ RUST ──────────────────────────────────────────
  │ fn main() {
  │     println!("hello");
  │ }
  └─────────────────────────────────────────────────

Every row sits on COVEN_CODE_PANEL_BG and is fitted to the frame — clipped when it overruns, padded when it falls short — so the block reads as one continuous slab with a square right edge. Rules span the real width. Body lines truncate with …. A tagged fence gets an accent-colored chip.

Review attention

  • The edge glyph and the chip are independent. An untagged fence still opens with ┌; it just has no chip. My first version keyed the glyph off the label and opened untagged fences with └ — the visual check caught it, and the test that should have caught it had an || that accepted either edge. Both are fixed.
  • The fence tag is model output, so it is validated against a conservative identifier set plus a length cap and rejected outright rather than mangled into a chip.
  • Colors move off Color::Yellow/Color::White onto the COVEN_CODE_* tokens, per follow-up 4 of docs/tui-ux-accessibility-review.md — the named ANSI colors are terminal-theme dependent, so contrast through them cannot be reasoned about.

The first commit is not mine to own

cargo fmt --all -- --check (rust-ci.yml:52) fails on a pristine checkout of main — render_response_reader landed unformatted in #181:

$ git checkout origin/main && cd src-rust && cargo fmt --all -- --check
Diff in .../crates/tui/src/response_reader.rs:179

The gate is red on main and every new PR inherits it, so the fix is here as its own commit. Pure formatting, no behavior change.

Tests

The live render_markdown fence path had no tests before this — only the unreachable messages::render_code_block is snapshotted (tests/render_snapshots.rs:214). Added 8 cases: surface fill, frame-width fitting, the chip, matching open/close edges, untagged fences, tag rejection, truncation, and narrow frames down to zero width (which is how I found a rule-overflow bug at widths below the chip).

Verification

cd src-rust
cargo fmt --all                                          # clean
cargo clippy --workspace --all-targets -- -D warnings    # clean, no warnings
cargo test --workspace                                   # 32 suites, 0 failures
git diff --check                                         # clean

Manual TUI validation per AGENTS.md, tmux 80x24:

tmux new-session -d -s cc-md-test -x 80 -y 24
tmux send-keys -t cc-md-test "./target/debug/coven-code" Enter
tmux capture-pane -t cc-md-test -p

App starts clean, welcome box intact. The strings scripts/tui-tests/cases/02_startup.sh pins — Coven v, Tips for getting started, What's new, and the ❯ prompt — all still render, so the startup case is unaffected.

Known gaps

  • No syntax highlighting for fences. syntect is already a dependency and already wired for diffs (diff_viewer.rs:977); that is the obvious next step and is deliberately out of scope here to keep this diff reviewable.
  • messages::render_code_block remains dead code, kept alive only by its snapshot test. Not touched.

`cargo fmt --all -- --check` (rust-ci.yml:52) fails on a pristine
checkout of main: `render_response_reader` has a chained
`saturating_add` that rustfmt wants broken across lines. It landed
unformatted in #181, so the gate is currently red on main and every
new PR inherits the failure.

Pure formatting, no behavior change. Kept as its own commit so the
code-block work in this branch stays readable.

Signed-off-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
Fenced code in the transcript was drawn as a fixed 50-dash rule plus a
yellow gutter, with the raw source line pushed verbatim. Three problems
followed from that.

The block had no edges. Code inherited the app background, so a fence
bled into the prose above and below it and read as indented text rather
than as a block.

The rules ignored the frame. They were a hardcoded 50 dashes regardless
of terminal width, so they under- or overshot on every terminal that was
not exactly that wide.

Worst, long lines were clipped silently. `render_markdown` already
receives the frame width and the code path ignored it, so ratatui cut
the line at the buffer edge with no marker. A truncated line was
indistinguishable from a complete one, which matters most for exactly
the content a reader is most likely to copy.

Code rows now sit on COVEN_CODE_PANEL_BG and are fitted to the frame:
clipped when they overrun, padded when they fall short, so a block reads
as one continuous slab with a square right edge. Rules span the actual
width. Body lines truncate with an ellipsis so a cut is visible. A
tagged fence gets an accent-colored chip naming the language.

The fence colors move off Color::Yellow and Color::White onto the
COVEN_CODE_* tokens, per follow-up 4 of
docs/tui-ux-accessibility-review.md: the named ANSI colors are
terminal-theme dependent, so contrast there cannot be reasoned about.

Two details worth review attention:

- The edge glyph and the chip are independent. An untagged fence still
  opens with the opening edge; it just has no chip. An earlier version
  keyed the glyph off the label and opened untagged fences with the
  closing edge.
- The fence tag is model output, so it is validated against a
  conservative identifier set and a length cap and rejected outright
  rather than mangled into a chip.

Tests cover the surface fill, frame-width fitting, the chip, matching
open and close edges, tag rejection, truncation, and narrow frames down
to zero width. The live `render_markdown` fence path had no tests
before; only the unreachable `messages::render_code_block` was
snapshotted.

Signed-off-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
@vercel

vercel Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Ready Ready Preview Sep 15, 2026 8:36am UTC

Request Review

This branch was successfully deployed

1 active deployment
Preview — 17357262 Deployed Sep 15, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant