Skip to content

fix(parse): render inline <br> as a line break, not literal text - #47

Merged
do-md merged 1 commit into
do-md:mainfrom
123soulshell:fix/inline-html-br
Nov 7, 2026
Merged

do-md merged 1 commit into
do-md:mainfrom
123soulshell:fix/inline-html-br

Conversation

@123soulshell

Copy link
Copy Markdown
Contributor

Summary

<br> is inline raw HTML; CommonMark and GitHub render it as a hard line
break. The kernel's inline-HTML branch only matched paired tags (<u>,
<ins>, <sub>, <sup>, <kbd>, <mark>), so <br> fell through to a
plain text leaf and the tag itself was displayed.

Repro (read-only, @do-md/core-react 0.14.0):

const md = [
  "| 账号 | 5 小时滚动 |",
  "| --- | --- |",
  "| 账号1 | 4%<br>重置 11-02 18:18 |",
].join("\n");

<DOMDProvider initMd={md} editable={false}><DOMD /></DOMDProvider>

Actual: the cell shows 4%<br>重置 11-02 18:18 — the literal tag.
Expected: a line break inside the cell, the way GitHub renders the same
Markdown.

This shape is common in AI output: a comparison table in a task receipt has no
in-cell newline (GFM tables cannot express one), so models write the second
line as <br>. It also shows up mid-paragraph (第一行<br>第二行) and anywhere
else a hard break is written as inline HTML.

Fix

  • parseInline.ts — match bare <br> / <br/> / <br /> (any case) in the
    < branch and build the same wrapper shape the image branch uses: a hidden
    MdSymbol carrying the exact source text, plus a Br leaf next to it that
    renders the real <br>. The source text still lives in the model, so
    toMarkdown(parseMarkdown(md)) stays byte-exact.
  • getHTMLText.ts — a bare <br> at the start of a line now falls through to
    the inline parser instead of being claimed by the HTML-block path first
    (which rendered the tag as text inside a div).

Attributes are intentionally out of scope — <br class="x"> keeps its current
behavior, same as the other bare inline tags.

Verification

New harness — cd .packages/@do-md/core && sh scripts/verify-inline-br/run.sh —
28 assertions:

  • paragraph and table-cell breaks produce exactly one Br node, and the
    visible text contains no tag;
  • <br>, <br/>, <br />, <BR>, <Br/> all parse and round-trip
    byte-exact;
  • nested inside <u>, leading <br> at line start, canonical-table fixed
    point;
  • non-regressions: <https://…> autolink, fenced code, <br class="x"> stay
    literal text.

Also ran: verify-table-after-paragraph 20/20, verify-softbreak 32/32,
kernel tsc --noEmit clean. verify-empty-blocks reports 40/42 with the same
two pre-existing failures as on main (empty checkbox / empty blockquote
shapes, unrelated).

Rendered check in a jsdom consumer (read-only DOMDProvider): the table cell
now contains <br class="DOMD-Br"> between the text spans, and the tag is no
longer visible.

Happy to adjust the approach (node shape, scope, or where the tag falls
through) if you'd prefer something different.

`<br>` is inline raw HTML — CommonMark/GitHub render it as a hard line
break — but the inline-tag branch only matched *paired* tags (`<u>`,
`<ins>`, `<sub>`, `<sup>`, `<kbd>`, `<mark>`), so `<br>` fell through to a
plain text leaf and the tag itself was displayed.

It shows up wherever a newline cannot be written any other way, most
commonly a GFM table cell produced by an AI answer
(`| 4%<br>重置 18:18 |` — tables have no in-cell newline) and
mid-paragraph (`第一行<br>第二行`).

Parse `<br>` / `<br/>` / `<br />` (any case) into the same Plain group
shape the image branch uses: a hidden MdSymbol carries the exact source
text, so `toMarkdown(parseMarkdown(md))` stays byte-exact, and a Br leaf
next to it renders the real `<br>`. A bare `<br>` at the start of a line
now also reaches the inline parser; the HTML-block path used to claim it
first (it rendered the tag as text inside a div).

Verification: new `scripts/verify-inline-br` harness (28 assertions:
mid-paragraph, table cell, self-closing/case spellings, nested `<u>`,
leading `<br>`, autolink + code-fence regressions, byte-exact round-trip,
canonical table fixed point). Existing suites unchanged:
`verify-table-after-paragraph` 20/20, `verify-softbreak` 32/32;
`verify-empty-blocks` 40/42 with the same two pre-existing failures as on
`main`. Kernel `tsc --noEmit` clean.
@vercel

vercel Bot commented Nov 2, 2026

Copy link
Copy Markdown

@123soulshell is attempting to deploy a commit to the do-md's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

All contributors have signed the CLA. Thank you!
Posted by the CLA Assistant Lite bot.

@123soulshell

Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

github-actions Bot added a commit that referenced this pull request Nov 2, 2026
@do-md
do-md merged commit 1fedac7 into do-md:main Nov 7, 2026
1 of 3 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Nov 7, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants