- splitLines(): join .codeline blocks with "" not "\n" — newline text
nodes under pre-wrap rendered an extra line box per logical line, so
every gutter number drifted one row per line
- renumber(): geometry-driven placement — one gutline per visual row of
the code body, each number assigned to the row matching its line's
measured offsetTop; verified second pass re-runs if a partially-filled
last wrapped row reflows after the gutter rebuild
- app.css: mobile media query gives .code .gutter the same 13px font as
.code so gutter rows and code rows share one line box at 375px
Verified in local build with headless chromium (long unbroken token on
line 5, wrap ON): all numbers align with line starts, no misalignment,
no horizontal scroll at 1400x900 and 375x812.