Skip to content

A wrapped line starts where it should, not one space in - #726

Merged
PathGao merged 1 commit into
masterfrom
fix/soft-line-anchor-indent
Aug 28, 2026
Merged

A wrapped line starts where it should, not one space in#726
PathGao merged 1 commit into
masterfrom
fix/soft-line-anchor-indent

Conversation

@PathGao

@PathGao PathGao commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

What this is

A paragraph of three or more source lines rendered every line but its first with a leading space in the preview. The Markdown has no such space. Reported by @PathGao with a screenshot of a plain CJK document, where the indent is unmistakable because every line starts at a different place from the one above it.

No issue filed for it.

Mechanism

Not the block patcher (#632), which was the first suspect — this predates it. render.hardbreaks makes comrak end every source line with <br /> followed by a newline, and that newline is collapsible whitespace. A browser drops it, but only under one condition: it has to still be at the start of the line. CSS Text 3 removes a sequence of collapsible spaces at the beginning of a line; it does not remove one that follows a box.

processSoftLineAnchors (#541) inserts a <span class="source-line-anchor"> beside each <br> so a soft line break has something with an offsetTop for split-view scroll sync to resolve to. That span is display: inline-block — being a box is the whole point of it, since a <br> generates none — and it was inserted with insertBefore(anchor, br.nextSibling), which lands it in front of comrak's newline. The newline was then a space between two boxes, which is drawn.

Measured in Chromium, three paragraphs identical but for where the anchor sits, x of the first glyph on the second line:

line one<br />\nline two                    →  8.0px   (no anchor)
line one<br /><span></span>\nline two      →  8.0 + one space   ← what shipped
line one<br />\n<span></span>line two      →  8.0px

The anchor now goes behind the leading whitespace: the text node is split so the newline stays on the break's side of it. previewAnchor reads only offsetTop off the anchor, and that is the same on either side of a zero-width space.

Scope

The <br> is still untouched, for the reason the existing comment gives. Nothing about which blocks qualify for anchors (LINE_ANCHOR_MIN_SPAN) changed, so short paragraphs — which never had the defect, having no anchors — render exactly as before.

splitText would have been the obvious way to split the text node; the render-protocol DOM the tests drive doesn't implement it, so the split is done with createTextNode + insertBefore, the two APIs that module already restricts itself to.

Tests

the newline behind a break stays in front of the anchor in scripts/scrollSyncBlockMapping.test.ts, beside the anchors' other tests. It walks the anchors of a four-line paragraph and asserts each one's previous sibling is whitespace and the node before that is the <br>.

Revert the fix and keep the test: it goes red (an anchor sits behind the whitespace, not in front of it). The other 30 tests in that file stay green either way, which is the point — they pin where the anchors are in the source, and nothing about that changed.

Verification

npm audit          found 0 vulnerabilities
npm run check      816 files, 0 errors, 0 warnings
npm test           987 pass, 0 fail
npm run test:vitest 401 pass, 0 fail   (not in the list above, but test.yml runs it)
cargo test         164 pass, 0 fail

Not verified: the pixel behaviour was measured in Chromium, not in the WKWebView the macOS app actually renders in. The rule involved is plain CSS whitespace collapsing rather than anything engine-specific, but I did not put the built app in front of it and count pixels.

comrak writes a newline behind every `<br>`, and a browser drops that
newline only while it is still at the start of the line. The soft-line
anchor #541 added is an `inline-block` — a box — and it was inserted
between the break and that newline, which made the newline a space
BETWEEN two boxes instead of one at the start of a line. So every source
line but the first of a paragraph spanning three lines or more opened
with an indent nobody typed.

The anchor now goes behind the whitespace rather than in front of it.
Its position on screen — the only thing `previewAnchor` reads it for —
is the same either way.
@PathGao
PathGao merged commit 62dad42 into master Aug 28, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant