Skip to content

fix(storage-walker): keep <br/> inside list items as a hard line break - #248

Merged
pchuri merged 3 commits into
mainfrom
fix/list-item-hard-break
Sep 30, 2026
Merged

pchuri merged 3 commits into
mainfrom
fix/list-item-hard-break

Conversation

@pchuri

@pchuri pchuri commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

Pull Request Template

Description

Fixes #242. In storage → markdown, <br/> inside a list item became a space. The walker emitted \n, and the item's inline whitespace collapse (added in #239) then erased it.

While the walker is inside whitespace-collapsed inline content (list items, table cells, task bodies), <br/> now emits a private-use HARD_BREAK sentinel (U+E003) that survives the collapse. The outermost such context resolves it:

Context Storage Markdown Back to storage
List item <ul><li>line1<br/>line2</li></ul> - line1\
line2
<li>line1<br />
line2</li>
Task body …<ac:task-body>a<br/>b</ac:task-body>… - [ ] a\
b
<li>[ ] a<br />
b</li>
Table cell <td>a<br/>b</td> | a<br>b | <td><p>a<br />b</p></td>
  • List items and task bodies use a CommonMark backslash hard break, with the continuation indented to the content column via LIST_INDENT. A trailing-two-space break would be stripped by cleanupOutsideFence. Continuation lines that CommonMark would otherwise parse as a new block are backslash-escaped: - , * , + , 1. , 2) , # , >, a backtick or tilde fence, ---, ===, * * *, and GFM table delimiter rows such as --- | ---. An odd run of trailing backslashes before a break (C:\temp\) gets one more \ so it stays literal and the break survives. Inline syntax such as **bold** or a code span is left alone.
  • Table cells use inline <br>, because GFM rows cannot span lines. markdownToStorage already normalizes <br> to <br /> ([BUG] Markdown line breaks generate invalid XHTML <br> instead of <br /> (fails on Confluence On-Premise) #210). Lists and tasks nested in a cell leave the sentinel for the cell to resolve.
  • Unchanged:
    • Top-level <br/> still emits \n, which is a soft break. This is the same as main and out of scope for this PR.
    • Blocks inside a list item (blockquote, callout) behave as before.
    • <br/> inside <code> stays a space, since a code span cannot hold a line break.
  • Sentinel escaping: literal U+E003 in user content is reversibly escaped in decodeEntities, like U+E000/U+E001/U+E002 (QUOTE_MARK, fix(storage-walker): keep code blocks inside blockquotes and callouts byte-exact #249). Task bodies use the shared list-item path from fix(storage-walker): preserve nested task lists in storage → markdown #245, so their breaks go through the same code.

Important

Behavior change (release notes): this affects every <br/> inside a table cell, not just ones in list items. That includes header cells and <p>, headings, blockquotes, panels, lists, tasks, links and <pre> inside cells, as well as tables nested in lists. Cells previously rendered those breaks as a space and now render them as inline <br>, which round-trips to <br />. For example, | > a > b | becomes | > a<br>b |.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Performance improvement
  • Code refactoring

Testing

  • Tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

npx jest: 1453/1453 passed (after merging current main). npx eslint lib tests: clean. The new #242 suite in tests/macro-converter.test.js asserts exact output and markdown → storage → markdown round trips. 27 of its cases fail on main.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • Any dependent changes have been merged and published in downstream modules

Screenshots (if applicable)

N/A

Additional Context

  • The parity fixture 07-tables.expected.md changes intentionally: line one line two becomes line one<br>line two.
  • lib/html-to-markdown.js is a separate converter and still renders <br> as a space in list items and table cells. That is a follow-up.

A <br/> inside a list item rendered as a newline, which the item's
inline whitespace collapse then turned into a single space.

While walking whitespace-collapsed inline content (list items, table
cells, task bodies), <br/> now emits a private-use HARD_BREAK sentinel
(U+E002) that survives the collapse; the outermost such context
resolves it:
- list items and task bodies emit a CommonMark backslash hard break
  with the continuation indented to the content column, and escape
  continuation lines that would otherwise start a block (list marker,
  heading, blockquote, fence, setext underline, thematic break);
- table cells emit inline <br>, since GFM rows cannot span lines.

Both forms convert back to <br /> on markdown → storage. <br/> inside a
code span and at the top level keeps its previous output. Literal
U+E002 in user content is reversibly escaped like the other sentinels.

Fixes #242
- Move HARD_BREAK to U+E003 so it does not collide with the U+E002
  sentinel planned for blockquote handling.
- Double an odd trailing backslash run before a hard break
  (`C:\temp\<br/>`) so it stays literal and the break survives the
  round trip.
- Escape a continuation line that is a GFM table delimiter row, which
  would otherwise turn the item's text into a table.
- Extract renderInlineRun for the shared collapse + resolve pipeline.
- Tighten tests to exact output and full round trips, and cover header
  and multi-cell rows, 10. markers, link labels, inline HTML,
  ac:link-body, lists in callouts/blockquotes and <pre> in cells.
# Conflicts:
#	lib/markdown-cleanup.js
#	lib/storage-walker.js
#	tests/macro-converter.test.js
#	tests/markdown-cleanup.test.js
@pchuri
pchuri merged commit a087c64 into main Sep 30, 2026
6 checks passed
@pchuri
pchuri deleted the fix/list-item-hard-break branch September 30, 2026 01:06
github-actions Bot pushed a commit that referenced this pull request Sep 30, 2026
## [2.25.8](v2.25.7...v2.25.8) (2026-09-30)

### Bug Fixes

* **storage-walker:** keep <br/> inside list items as a hard line break ([#248](#248)) ([a087c64](a087c64)), closes [#242](#242)
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 2.25.8 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

<br/> inside a list item becomes a space in storage → markdown

1 participant