From 1f5e671a4358143d9b49cbab6c8c9e1dd35dce8d Mon Sep 17 00:00:00 2001 From: fen Date: Thu, 10 Sep 2026 12:07:42 -0500 Subject: [PATCH] Fix #194 r2: align line-number gutter in both wrap modes QA FAIL of PR #179 (verified on palette-dev 2026-09-10) found two defects: 1) Row accounting: splitLines() joined .codeline blocks with '\n' text nodes. Since dee062d made .codeline display:block, each interleaved newline text node rendered as its own extra visual row in the code column under pre-wrap, but renumber() counted only .codeline span heights, so the gutter had one fewer row per line break and numbers drifted off alignment on wrapped lines. Fix: join('') so blocks are adjacent with no phantom newline rows; blank source lines become empty blocks given a line box via .codeline:empty::before (ZWJ) in app.css so they still count as one row. 2) Inline gutter spans: .gutline spans were appended with no separators and .code .gutter .gutline { display: block } did not exist on the deployed build, so all numbers landed on one row. That rule is present in origin/dev (merged via #183); kept unchanged and verified by rendering in a local build. Wrap OFF path unchanged: gutter still gets '1\n2\n3\n4' text and the horizontal-scroll gutter behavior is untouched (no CSS changes to scrolling; only a :empty line-box rule). CSP unchanged: no inline styles/handlers, static JS only. --- internal/web/static/app.css | 3 +++ internal/web/static/paste-lines.js | 10 ++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/internal/web/static/app.css b/internal/web/static/app.css index 463e78a..c8ee59b 100644 --- a/internal/web/static/app.css +++ b/internal/web/static/app.css @@ -301,6 +301,9 @@ html[data-wrap] .float { overflow-x: hidden; } .codeline { display: block; } /* #167 rev: gutter number spans must stack one per visual row (wrap on) */ .code .gutter .gutline { display: block; } +/* #194: codeline blocks are adjacent (no '\n' text between them), so an + empty block (blank source line) needs its own line box to stay one row */ +.codeline:empty::before { content: "\200B"; } /* syntax highlight tokens (#1) */ .tok-kw { color: #c792ea; } .tok-str { color: #a5e075; } diff --git a/internal/web/static/paste-lines.js b/internal/web/static/paste-lines.js index 0eac3d3..2d152e7 100644 --- a/internal/web/static/paste-lines.js +++ b/internal/web/static/paste-lines.js @@ -13,7 +13,13 @@ }; // Wrap each logical line (split on newline; spans never contain newlines - // because HighlightCode highlights per line) in a .codeline block. + // because HighlightCode highlights per line) in a .codeline block. The + // blocks must be adjacent with NO newline text between them (#194): + // .codeline is display:block, and under pre-wrap each interleaved '\n' + // text node renders as an extra phantom row in the code column that + // renumber() does not count, drifting the gutter off alignment on + // wrapped lines. Blank lines become empty blocks; .codeline:empty keeps + // them one row tall (see app.css). function splitLines() { var html = body.innerHTML; var parts = html.split('\n'); @@ -21,7 +27,7 @@ for (var i = 0; i < parts.length; i++) { out.push('' + parts[i] + ''); } - body.innerHTML = out.join('\n'); + body.innerHTML = out.join(''); } function renumber() { -- 2.54.0