Skip to content

blockmap: a lazy-continuation admonition keeps its marker and its indent - #31

Merged
jbeda merged 1 commit into
jbeda:mainfrom
karlkfi:admonition-lazy
Aug 10, 2026
Merged

blockmap: a lazy-continuation admonition keeps its marker and its indent#31
jbeda merged 1 commit into
jbeda:mainfrom
karlkfi:admonition-lazy

Conversation

@karlkfi

@karlkfi karlkfi commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #19, which only covered half the construct. This is the half that was silently destroying callouts.

Problem

MkDocs and Python-Markdown accept both of these:

!!! tip "Blank line after the marker"

    Body indented four spaces.
!!! tip "No blank line"
    Body indented four spaces.

The first is a paragraph plus an indented code block, which #19 recognizes. The second is a single paragraph to CommonMark, because the indented body is a lazy continuation rather than a code block, so it never reaches that recognition and reflows as ordinary prose:

$ printf '!!! tip "Title here"\n    The recommended shape is the v2 API at\n    a decomposed resource. Second sentence here.\n' | mdreflow -
!!! tip "Title here" The recommended shape is the v2 API at a decomposed resource.
Second sentence here.

MkDocs then renders that as a paragraph beginning with a literal !!! tip. The callout is gone.

Why the marker alone is not enough

I tried the obvious fix first, a lineBoundary dialect rule like the GitHub alert marker. The marker survives, but the body loses its indent:

!!! tip "Title here"
The recommended shape is the v2 API at a decomposed resource.
Second sentence here.

Python-Markdown needs the 4-space indent, so that is still not a callout. The alert case works only because > is the paragraph's container prefix and gets reproduced; an admonition's indent is not a container prefix.

Proposal

build already handles exactly this shape for a footnote definition: a marker first line whose body must stay indented, done by appending to ContPrefix. An admonition needs the same plus an immovable marker line.

if admonitionMarkerRE.MatchString(trimmed[0]) {
    boundary[0] = true
    contPrefix += "    "
}

Result:

!!! tip "Title here"
    The recommended shape is the v2 API at a decomposed resource.
    Second sentence here.

Marker preserved, body reflowed, indent intact. No skip, and the body still gets sentence-per-line.

Verification

  • go test ./... green, plus a table case asserting both the boundary and the prefix.
  • Constructed edges: the blank-line spelling still routes through blockmap: reflow MkDocs admonition bodies under an opt-in dialect #19, prose merely opening with !!! is untouched (the marker pattern requires a type word), a marker with no title works, and one nested in a list item keeps its container prefix.
  • Full mkdocs build over the 263-file docset this was found on, before and after: structural page differences went from 7 to 0.

The two pages that still differ structurally are the reflow fixing latent bugs in that source, not regressions: a sentence wrapped so 10. landed at line start had been rendering as a stray <li>, and a heading that was missing from the page nav now appears.

Unrelated pre-existing failure, for your awareness

Fuzzing this branch surfaced an idempotency failure on "*\n\t**\n**\n:::\n0". It reproduces on unmodified main with the same seed, so it is not from this change and I have not tried to fix it. Happy to file it separately if it is not already known.

MkDocs and Python-Markdown accept an admonition whose body follows the
marker with no blank line between them. That spelling is one paragraph to
CommonMark: the indented body is a lazy continuation, not the indented code
block the blank-line spelling produces, so it never reaches the dialect
recognition added for the latter.

Reflowed as ordinary prose it stops being a callout. The marker joins the
body, and even with the marker pinned the body loses the 4-space indent the
extension requires, so the whole thing renders as a paragraph beginning
with a literal "!!! tip".

build already has this exact shape for a footnote definition: a marker
first line whose body must stay indented, handled by appending to
ContPrefix. An admonition needs that plus an immovable marker, so it gets
both.

Found by diffing a rendered mkdocs site before and after adopting
sentence-per-line across a 263-file docset: 7 of 17 callouts were being
destroyed, and no existing gate saw it, since the output is still valid
Markdown that builds.
@jbeda

jbeda commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Merged (rebased onto the fresh v0.1.5 — your CHANGELOG entry moved to the new Unreleased section with a #31 reference). Took it ungated after weighing the dialect question: boundary-protection for dialect markers is the always-on posture here (GitHub alerts, Docusaurus fences, Hugo shortcodes all get it regardless of dialect), and this is render-preserving under goldmark, so it belongs in that family rather than behind --dialect mkdocs, which stays reserved for render-changing recognition. The 7→0 mkdocs-build verification is the kind of evidence that makes these easy to take. Your idempotency find is filed as #32 with credit — confirmed pre-existing, confirmed backstop-absorbed for users, and flagged as the second ::: -neighborhood find in a day. It's first in line for v0.1.6, which at the current rate you're setting will be soon.

jbeda added a commit that referenced this pull request Aug 10, 2026
)

A list item whose marker line has an empty body carries its prose on a
later, indented physical line, so writeParagraph's firstLinePrefix — the
joint context the thematic-break escape needs — is that line's own leading
whitespace, pure container indentation goldmark consumes before the block
scan. It was normalized for blockquote markers but not for that indent, and
a leading tab left in place disqualified the whole line from isThematicBreak
(which counts only spaces as separators). So when the body "**"/"**"
reflowed to "** **" — four asterisks, a thematic break — the tab hid it
from the escape, and the join silently ended the list and dropped the
following ::: paragraph out of it. A render change; the render backstop was
reverting it, which is why public Format looked idempotent on the shape.

firstLinePrefix now strips leading spaces/tabs after the blockquote strip:
a bare-indent prefix contributes "" to the joint spelling, while a "* "/"- "
marker char (itself part of a thematic-break run) still survives. The body
is escaped and reflows normally.

Found by @karlkfi fuzzing the #31 branch on "*\n\t**\n**\n:::\n0"; seed
checked in. Second find in the ::: boundary neighborhood after cf1a42a.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

2 participants