From 12de9dbe40de79f4cbdb7699cdd5112ad0de1bf2 Mon Sep 17 00:00:00 2001 From: Christian Boos Date: Sat, 7 Nov 2026 00:08:06 +0100 Subject: [PATCH 1/2] Test that line numbers keep step with the code in both themes A browser test over the Read tool fixture: in a line-numbered Pygments block, the number column and the code column must span the same height and start at the same top, in classic and in minimal. Classic passes; minimal fails by 11px over 20 lines (#364). Co-Authored-By: Claude Fable 5.1 --- test/test_minimal_components_browser.py | 48 +++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/test/test_minimal_components_browser.py b/test/test_minimal_components_browser.py index aa0e90e7..ef0cd333 100644 --- a/test/test_minimal_components_browser.py +++ b/test/test_minimal_components_browser.py @@ -559,3 +559,51 @@ def test_a_markdown_image_stays_in_its_message(page: Page, tmp_path: Path) -> No ) assert widths[2] == 3000, "the image did not load" assert widths[0] <= widths[1] + 0.5, widths + + +READ_TOOL = TEST_DATA / "read_tool_pygments.jsonl" + +# A line-numbered Pygments block: `td.linenos > div > pre` holds the numbers, +# `td.code > div > pre > code` the code. Both columns' line boxes must agree +# or the numbers drift away from their lines down a long file (#364). +LINE_NUMBERED_BLOCK = """ +() => { + const table = [...document.querySelectorAll('.highlighttable')] + .find(t => !t.closest('.preview-content')); + const numbers = table.querySelector('td.linenos pre'); + const code = table.querySelector('td.code pre code'); + const content = (el) => { + const s = getComputedStyle(el); + return el.getBoundingClientRect().height + - parseFloat(s.paddingTop) - parseFloat(s.paddingBottom); + }; + const top = (el) => { + const s = getComputedStyle(el); + return el.getBoundingClientRect().top + parseFloat(s.paddingTop); + }; + return { + lines: numbers.textContent.trim().split('\\n').length, + numbersHeight: content(numbers), + codeHeight: content(code), + numbersTop: top(numbers), + codeTop: top(code), + }; +} +""" + + +@pytest.mark.browser +class TestLineNumberedCode: + @pytest.mark.parametrize("theme", ["classic", "minimal"]) + def test_line_numbers_keep_step_with_the_code( + self, page: Page, tmp_path: Path, theme: str + ) -> None: + page.goto(_render(tmp_path, READ_TOOL, theme).as_uri()) + page.evaluate( + "document.querySelectorAll('details').forEach(d => d.open = true)" + ) + box = page.evaluate(LINE_NUMBERED_BLOCK) + assert box["lines"] >= 20, box + pitch = (box["numbersHeight"] / box["lines"], box["codeHeight"] / box["lines"]) + assert abs(box["numbersHeight"] - box["codeHeight"]) < 1, (box, pitch) + assert abs(box["numbersTop"] - box["codeTop"]) < 1, box From 398be6bcbe0758f6d73a03f8cf0185692ff7950d Mon Sep 17 00:00:00 2001 From: Christian Boos Date: Sat, 7 Nov 2026 00:09:19 +0100 Subject: [PATCH 2/2] Minimal theme: keep line numbers in step with the code (#364) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In a line-numbered Pygments block the number column is a bare
,
the code column a 
. The shared sheet's `code { line-height:
1.5 }` reached that , while the minimal theme gives both 
s
1.55 — so the numbers outran the code by half a pixel a line, a whole
line every thirty. Classic has 1.5 on both and never showed it.

The minimal `pre code` reset now inherits the line height too. Minimal
CSS only: the classic page and the shared sheets are untouched, and the
three regenerated snapshots are the minimal ones, additive.

Co-Authored-By: Claude Fable 5.1 
---
 .../html/templates/components/minimal/layout.css  |  5 +++++
 test/__snapshots__/test_snapshot_html.ambr        | 15 +++++++++++++++
 2 files changed, 20 insertions(+)

diff --git a/claude_code_log/html/templates/components/minimal/layout.css b/claude_code_log/html/templates/components/minimal/layout.css
index 5ce44e50..55595f77 100644
--- a/claude_code_log/html/templates/components/minimal/layout.css
+++ b/claude_code_log/html/templates/components/minimal/layout.css
@@ -916,8 +916,13 @@
     color: inherit;
 }
 
+/* The shared sheet's `code { line-height: 1.5 }` reaches the  that
+   Pygments wraps a line-numbered block's code column in, while the number
+   column is a bare 
 at this theme's 1.55: the numbers then outrun
+   the code by a line every thirty lines (#364). */
 .theme-minimal #transcript pre code {
     font-size: inherit;
+    line-height: inherit;
     padding: 0;
     background: none;
 }
diff --git a/test/__snapshots__/test_snapshot_html.ambr b/test/__snapshots__/test_snapshot_html.ambr
index 212bd99d..c7fc8ab8 100644
--- a/test/__snapshots__/test_snapshot_html.ambr
+++ b/test/__snapshots__/test_snapshot_html.ambr
@@ -29199,8 +29199,13 @@
       color: inherit;
   }
   
+  /* The shared sheet's `code { line-height: 1.5 }` reaches the  that
+     Pygments wraps a line-numbered block's code column in, while the number
+     column is a bare 
 at this theme's 1.55: the numbers then outrun
+     the code by a line every thirty lines (#364). */
   .theme-minimal #transcript pre code {
       font-size: inherit;
+      line-height: inherit;
       padding: 0;
       background: none;
   }
@@ -44992,8 +44997,13 @@
       color: inherit;
   }
   
+  /* The shared sheet's `code { line-height: 1.5 }` reaches the  that
+     Pygments wraps a line-numbered block's code column in, while the number
+     column is a bare 
 at this theme's 1.55: the numbers then outrun
+     the code by a line every thirty lines (#364). */
   .theme-minimal #transcript pre code {
       font-size: inherit;
+      line-height: inherit;
       padding: 0;
       background: none;
   }
@@ -69426,8 +69436,13 @@
       color: inherit;
   }
   
+  /* The shared sheet's `code { line-height: 1.5 }` reaches the  that
+     Pygments wraps a line-numbered block's code column in, while the number
+     column is a bare 
 at this theme's 1.55: the numbers then outrun
+     the code by a line every thirty lines (#364). */
   .theme-minimal #transcript pre code {
       font-size: inherit;
+      line-height: inherit;
       padding: 0;
       background: none;
   }