From 5fdb34a059b8d26c3d1459d9752a1f00476b02b0 Mon Sep 17 00:00:00 2001 From: fen Date: Thu, 10 Sep 2026 12:11:47 -0500 Subject: [PATCH] Fix #167: align gutter numbers with actual rendered line rows (wrap ON) paste-lines.js joined .codeline spans with '\n' text nodes that pre-wrap renders as extra line boxes, and renumber() counted per-line span heights (rounding) instead of real geometry, so gutter numbers drifted off their line starts on wrapped lines. Fix: join spans with '' (blocks need no newline text), and build one .gutline per visual row measured from the code body's real height, placing each number at the visual row whose top matches its .codeline's measured top. Wrap OFF path unchanged. Mobile font-size rule added so gutter row pitch matches code rows at 375x812. --- internal/web/static/paste-lines.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/internal/web/static/paste-lines.js b/internal/web/static/paste-lines.js index a71d8ba..02944a6 100644 --- a/internal/web/static/paste-lines.js +++ b/internal/web/static/paste-lines.js @@ -54,7 +54,16 @@ // is closest to its line's top. var bodyTop = body.getBoundingClientRect().top; var lh = parseFloat(getComputedStyle(body).lineHeight) || 1; - var totalRows = Math.max(lines.length, Math.ceil(body.getBoundingClientRect().height / lh)); + var maxBottom = 0; + for (var j = 0; j < lines.length; j++) { + var btm = lines[j].getBoundingClientRect().bottom - bodyTop; + if (btm > maxBottom) maxBottom = btm; + } + // Rows needed through the last visual row of the deepest line (ceil, so a + // partially-filled last row still gets its gutter span). + // Rows needed: floor(last line bottom / lh) + 1 — the row a line STARTS on + // must exist even when its bottom lands exactly on the body's edge. + var totalRows = Math.max(lines.length, Math.floor(maxBottom / lh) + 1); gutter.textContent = ''; var frag = document.createDocumentFragment(); var spans = [];