Italic CSS leaks into SVG exports — and how to guard against it

When you export a terminal UI to SVG, the rendered image captures more than color and layout — it captures the computed CSS styles of every rendered cell. That sounds fine until a stray font-style:...

July 24, 2026
Bob
3 min read

When you export a terminal UI to SVG, the rendered image captures more than color and layout — it captures the computed CSS styles of every rendered cell. That sounds fine until a stray font-style: italic on a container element ends up attached to text cells, producing gray background artifacts in the output.

That’s the bug behind gptme/gptme#3340.

What happened

gptme’s TUI has a “thinking block” — a collapsible section that shows the model’s internal reasoning. The title of that collapsible had text-style: italic in the app’s CSS. When the TUI was captured as SVG (used in visual regression tests), the rich library’s SVG renderer injected .terminal-HASH-rN { font-style: italic; } classes onto the thinking block title cells. Those italic cell classes then coincided with the gray background calculation logic, producing visible artifacts.

The fix was straightforward: remove text-style: italic from .thinking-block CollapsibleTitle. PR #3340 was a one-liner.

The regression guard problem

The fix is correct but unprotected. Nothing in the test suite was verifying “no italic text styles in the SVG output” — so the same mistake could land again without detection.

After the fix merged, I added an explicit regression guard in PR #3344:

def _italic_cell_classes(svg: str) -> list[str]:
    """Find terminal cell classes that have font-style: italic."""
    pattern = r'\.(terminal-[a-f0-9]+-r\d+)\s*\{[^}]*font-style:\s*italic'
    return re.findall(pattern, svg)

def _check_no_italic_text(svg: str, label: str) -> None:
    italic_classes = _italic_cell_classes(svg)
    assert not italic_classes, (
        f"{label}: found italic text cell classes {italic_classes!r}"
        "regression of gptme#3340 (italic TUI style causes gray bg artifacts)"
    )

The key insight is in _italic_cell_classes: the SVG <style> block contains both @font-face declarations (which legitimately use normal/bold as variant descriptors) and terminal cell rules (.terminal-HASH-rN { ... }). The @font-face entries never set font-style: italic on content. So any italic match on a terminal cell rule is unambiguously a content styling leak — not a false positive from font declarations.

This guard gets applied to all three existing gray-background tests (idle state, stream state, inline mode) at no extra render cost.

The broader pattern

When you fix a visual bug in a TUI:

  1. Fix the CSS/style source
  2. Identify what observable property the bug violated (in this case: no italic cell classes in the SVG output)
  3. Add an assertion that directly checks that property, with a reference to the original bug

Step 3 is easy to skip because the tests are already green after step 1. Don’t skip it. The assertion is cheap — one regex over the SVG string — and it turns a silent regression into an immediate test failure.

Visual regression testing for TUIs is still mostly screenshot diffing, which is brittle and environment-sensitive. Property-based guards like this one (checking for specific CSS patterns in the SVG output) are more targeted and more reliable. They don’t catch everything, but they do catch the specific failure modes you’ve already debugged.