Agent chat: box-drawing diagrams join and scroll; unlabeled code blocks drop the Plain text row

TE
5d ago
+484-48

Leo (2026-11-01), on an agent reply's unlabeled fence holding a tree diagram: "could we render this type of thing better in cmux-next"

An unlabeled fence (no language, or text) that holds box-drawing characters (├─ └─ │) now draws as a diagram:

  • Connectors join. Its lines sit at line-height: 1.15, each exactly one line tall, in ui-monospace, "SF Mono", Menlo, "DejaVu Sans Mono", monospace, so every │ meets the one under it. Before, 12px glyphs on 20px lines left a band of about 6px under every connector. SF Mono (ui-monospace) and Menlo both carry the box-drawing block (checked with CoreText), and their │ ink is 1.37em and 1.27em tall, so at 1.15 the strokes overlap rather than just touching. A fixed ratio rather than normal: with normal, a fallback glyph on a line (the reference's ↓) makes that one line taller and opens a gap again.
  • Long lines scroll inside the card. The diagram draws on the plain card (never Pierre, never wrapped). Its pre scrolls horizontally inside the rounded card, which stays inside the column.
  • Quieter header for every unlabeled fence. No "</> Plain text" row: the card's actions (copy, plus wrap and open preview where the card has them) float in its top-right corner over the card's own tint and show while the pointer is on the card, focus is inside it, or one of its popups is open. A fence with a language keeps its row unchanged.
  • .cv-codeblock gets min-width: 0; max-width: 100%, so no card grows past its column inside a flex or grid parent.
  • The virtual transcript's estimate (model.ts codeCardHeight, used by blockHeight) drops the 45.5px header for an unlabeled fence and uses 1.15 lines for a diagram, so off-screen rows reserve the drawn height.

Syntax-highlighted blocks are unchanged. The box-drawing check (codeShape.ts isDiagram) runs once per card render, memoized on the code; nothing runs per token across rows.

Design spec: leo-workspace design/chat-transcript.md (the code-block diagram rules land there with this PR's number).

Gallery

agent-pane.code-block-diagram compares three header looks for an unlabeled card (corner copy on hover, a muted label row, the old labeled row) and three line heights (1.15, normal, the old 20px). Picked and shipped: corner copy, 1.15.

Tests

webviews/test/code-block-diagram-browser.test.tsx renders the reply in Chromium with the real stylesheets:

  • every vertical join (│ or ├ over │, ├ or └ in the same column) has a gap of at most 0.5px between the two glyph boxes;
  • a 200-character line: overflow-x: auto, white-space: pre, scrollWidth > clientWidth, the card inside the column, and its last character visible once scrolled to the end;
  • the unlabeled card has no label or icon, its header floats (not in flow), hidden at rest and shown on hover, and the body starts 13px from the card's top;
  • the estimate codeCardHeight gives for the tree is within 1px of the drawn card (scrollbar taken from the page);
  • a ```python fence keeps its in-flow "Python" row.

Run on the fleet Linux pool (cmux-ci test, CMUX_BROWSER_TESTS=1):

base (ca6b37af) source filesthis branch
connectors meetfail: gap 6px at line 1, column 0pass
200-char line scrollsfail: not drawn as a diagram card (the scroll asserts already held on the SSR plain card)pass
unlabeled headerfail: label "Plain text", icon, in flow, body at 46pxpass
labeled header keptpasspass

code-handoff-height.test.ts (Chromium and WebKit) and transcript-content-font.test.ts (light and dark) pass on the branch, as do the transcript layout tests that use the estimator (transcript-prepared-cache, message-placement, coderouter-row-heights, scroll-intent-rail, transcript-collapsed-tool-runs, transcript-worked-fold, chat-minimap: 52 pass, 0 fail with the diagram file). check:design-tokens, check:layers, check:popup-hover-reveals and the other check pieces, vp lint and vp fmt on the changed files, and the type check are clean.

Risk: every unlabeled fence (not only diagrams) loses its header row; a long first line can sit under the hover-revealed actions while the pointer is on the card. Labeled fences, Pierre highlighting and the code-handoff fade are unchanged for anything that isn't a diagram.

🤖 Generated with Claude Code

Agent session: agent session