Skip to content

HardBreakSpaces normalization can emit a marker the parser will not honor (checkbox-only line swallows it) #40

Description

@jbeda

Emission-side sibling of #39. That fix made detection consult goldmark's per-line verdict; marker emission still assumes the configured spelling works in every context.

Repro (raw core; production is shielded by the render backstop)

$ printf '* [X] <br>\n0\n' | mdreflow --hard-breaks=spaces -
* [X] <br>
0

The unchanged output is the render backstop absorbing the failure. What the raw core does: pass 1 detects the real <br> break, normalizes it to the configured spaces spelling, and emits "* [X] \n0" — but the task-list extension's taskListRegexp (^\[([\sxX])\]\s*) swallows the checkbox, the trailing spaces, and the line ending, so the emitted marker renders as a soft break: the hard break the source had is lost. Pass 2's detection (now AST-confirmed, #39) correctly reads the emitted line as soft and joins — single-pass idempotency and render preservation both violated in the raw core.

Only the spaces spelling is affected: a backslash marker survives (\s* stops at \, leaving "\\\n" to parse as a break with an empty flagged text node), and <br> is raw HTML in any context. Only reachable with --hard-breaks=spaces and a hard break whose cluster prose reduces to exactly the checkbox shape on the paragraph's first line of a task-list item.

Why no soak ever finds it

deriveOptions in fuzz_test.go derives only Mode and MaxWidth from the input hash; HardBreaks is always the zero value (HardBreakBr). The spaces and backslash styles are never fuzzed at all — a coverage gap worth noting alongside #24/#26 regardless of this bug.

Fix directions

  1. Per-position fallback, mirroring the existing first-output-line <br>→backslash fallback in writeParagraph: when attaching a spaces marker to a paragraph-first output line whose prose is exactly a checkbox shape and the paragraph is a list item's first child (p.Node parent chain answers this), fall back to the backslash spelling — render-preserving, one narrow position, style deviation only where the configured style cannot work.
  2. Broader doctrine: validate emitted markers the way detectHardBreak invents a hard break after a task-list checkbox (raw trailing-space rule vs goldmark AST) #39 validates detected ones (reparse-based), which would also catch any future extension with the same swallowing behavior — likely overkill until a second context shows up.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions