Skip to content

fix: escape a marker run only where a thematic break can open - #377

Merged
Azganoth merged 1 commit into
mainfrom
bug/thematic-break-marker-escape
Sep 3, 2026
Merged

fix: escape a marker run only where a thematic break can open#377
Azganoth merged 1 commit into
mainfrom
bug/thematic-break-marker-escape

Conversation

@Azganoth

@Azganoth Azganoth commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Summary

opensBlockConstruct tested the line's character class without its length, so a run of * or _ too short to open a thematic break was treated as one that could and kept the escape state.safe gave it. A line holding ** was written \*\*, and _ and __ were written \_ and \_\_. The line is literal text as written and as escaped, so the document was unchanged and the file gained backslashes the author never wrote.

THEMATIC_BREAK_PATTERNS now spells the construct rather than its character class: three markers or more, spaces and tabs between them, and nothing else. The three are counted across the line rather than inside one run of it, which is why this is not the run-length test the tilde branch beside it makes. A code fence needs its three consecutive; * * * and _ _ _ are breaks whose runs are one character each.

The check still reads the line only as far as it is known, which is the node's own slots plus the tail relaxAttentionEscapes assembles from the next sibling. A tail that cannot be read whole either adds markers, which keeps the escape, or adds something else, which fails the pattern and keeps it as well, so an unknown tail never drops a backslash the line turns out to need.

Related Issue

Closes #373

Verification

markdownCompatibility.test.ts gains nine rows in Escape precision, where this behavior is already pinned character by character.

Six lose an escape: **, _, and __ alone on a line, _ _ spending two markers across one, a two-marker run opening a paragraph's continuation line, and \_ __a__. Three keep the escape on the run the line starts with while the runs after it relax — \*\* \*, \* \* \*, and \_ \_ \_ — which is what pins the count to the line rather than to the run; those three pass before the change as well as after it. a paragraph line\n\*\*\* joins keeps the escape the document needs, since a break interrupts a paragraph.

Eight sources assert the written bytes and the reopened document together. Both spellings of a short run reopen as the same paragraph text, so closure holds either way and neither the corpus round trip nor the document-preservation guard can see the difference.

corpus/commonmark/blocks.md was measured by saving it once. Line 18 is now ** as the file writes it, and the breaks above it still round-trip as ***, ---, and _ _ _. What still differs there belongs to other classes: the \-- on line 20, the code span crossing a list-item boundary, and the blank lines the serializer inserts between adjacent headings.

pnpm check:frontend passes.

Not verified: no manual pass in the assembled application, and pnpm check:backend was not run. The change is serializer behavior with no rendered surface of its own, and it touches no Rust or Tauri code.

Notes

  • The underscore is fixed alongside the asterisk. Both reach this check through the same table, and the issue measured _ and __ as well as **. A lone * is untouched because nothing escapes it in the first place.
  • \_ __a__ moves out of keeps the escape the document needs. It stood there under a comment about a bullet marker, which only ever described its * sibling: an underscore opens no bullet, so the line's character class was the whole of what held it. _ __a__ is not a break, and the row now asserts the bytes and the reopened document together, which is what shows the strong span beside it survives.
  • No docs/ update. This narrows the positional check docs/decisions.md already records rather than adding a rule to it, and the thematic break form those documents describe is the one a real break is written in, which is untouched.
  • The -- two lines below in blocks.md stays escaped, as the issue scopes it out. A hyphen is not an attention character and never reaches this check.

The three markers are counted across the line rather than inside one run
of it, so `* * *` and `_ _ _` still open a break where `**` and `__` no
longer do. That is why the check is not the run-length test the tilde
branch beside it makes for a code fence.

`\_ __a__` loses its escape as a consequence. An underscore opens no
bullet marker, so the line's character class was the whole of what held
it, unlike its `*` sibling, which the bullet-marker check still holds.
@Azganoth Azganoth added the Bug Something isn't working label Sep 3, 2026
@Azganoth Azganoth self-assigned this Sep 3, 2026
@Azganoth
Azganoth enabled auto-merge (squash) September 3, 2026 05:36
@Azganoth
Azganoth merged commit 130ffcd into main Sep 3, 2026
3 checks passed
@Azganoth
Azganoth deleted the bug/thematic-break-marker-escape branch September 3, 2026 05:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The serializer escapes a marker run too short to open a thematic break

1 participant