Skip to content

Whitespace, tabs and columns ​

Where a space is required, where a tab reaches, blank lines, line endings and trailing whitespace.

Generated from resources/examples/edge-cases.md and resources/examples/core.md - edit the cases there, not here. Each case links the conformance fixture it produces.

Paragraph trailing whitespace ​

1 conformance fixture

Whitespace at the end of a paragraph's final line is stripped before rendering (CommonMark / Djot): abc renders without the trailing space. An interior two-space hard break is unaffected.

carve
abc
html
<p>abc</p>

Trailing whitespace boundaries ​

4 conformance fixtures

The trailing-whitespace strip (paragraph, NORMATIVE) removes whitespace at the end of the paragraph's SOURCE line before rendering. It does not touch spaces a construct produces during rendering, so a paragraph whose entire content is an all-space verbatim span keeps those spaces.

carve
!`  `
html
<p>  </p>

The same holds for a lone all-space code span, which keeps its <code> wrapper.

carve
`  `
html
<p><code>  </code></p>

... and for lone all-space math.

carve
$`  `
html
<p><span class="math inline" role="math">\(  \)</span></p>

A trailing NO-BREAK SPACE is content, not trailing whitespace: it is left in place and rendered as a character entity. Only ASCII whitespace is stripped.

carve
A trailing no-break space
html
<p>A trailing no-break space&nbsp;</p>

A marker separator is a space, never a tab ​

2 conformance fixtures

Every marker that takes a separator takes the space character: a tab after the marker leaves ordinary paragraph text. The definition term is shown because it was the last construct to agree - carve-js and carve-php read a tab as a term until carve#532.

carve
::	term
:  d
html
<p>::	term
:  d</p>

A space opens the same document as written.

carve
:: term
:  d
html
<dl>
  <dt>term</dt>
  <dd>d</dd>
</dl>

An invisible line does not cancel a blank-line separation ​

2 conformance fixtures

§17 L1 asks whether an item holds a blank-line-separated second paragraph. A line that renders nothing - a comment, a definition, an attribute line - is not a paragraph, which is why it cannot be the second one; the same fact means it cannot stand between the blank line and the paragraph that follows either. Delete the comment below and every implementation renders the item loose, so a construct that outputs nothing may not change that.

carve
- a

  %% n
  text
html
<ul>
  <li><p>a</p>
    <p>text</p>
  </li>
</ul>

An invisible line on its own is still not a second paragraph, so the item stays tight.

carve
- a

  %% n
html
<ul>
  <li>a</li>
</ul>

An invisible line before the blank does not cancel the separation ​

4 conformance fixtures

L1b AN INVISIBLE LINE DOES NOT CANCEL THE SEPARATION is pinned in one line order only. Corpus 186 writes the blank line first and the comment second; the mirror - the invisible line first, the blank second - was never pinned, and a rule whose answer turned on which of the two lines was authored first would need a reason there is none of. L1b's own argument does not mention order: a line that renders nothing "is not a separator, so the separation it appears to interrupt is intact". The two paragraphs are separated by the blank line either way, so the item is LOOSE either way.

carve
- para
  %% c

  more

x
html
<ul>
  <li><p>para</p>
    <p>more</p>
  </li>
</ul>
<p>x</p>

The other invisible kinds answer the same, which is what makes this one rule rather than a comment rule. A link reference definition registers and leaves the separation intact:

carve
- para
  [r]: /u

  more
html
<ul>
  <li><p>para</p>
    <p>more</p>
  </li>
</ul>

A footnote definition is the same answer one spelling over:

carve
- para
  [^f]: n

  more
html
<ul>
  <li><p>para</p>
    <p>more</p>
  </li>
</ul>

An attribute line is the row that shows the separation and the pending metadata are independent: the item is loose for the same reason as above, and A2 FLOAT FORWARD carries the attributes across the blank line onto the second paragraph.

carve
- para
  {.k}

  more
html
<ul>
  <li><p>para</p>
    <p class="k">more</p>
  </li>
</ul>

A tab after a heading, quote or caption marker leaves the line as prose ​

2 conformance fixtures

The definition markers already pin this rule; the block markers are the same one and nothing covered them. A tab after #, > or ^ is not the marker's separator, so no block opens.

carve
#	Heading

>	quoted
html
<p>#	Heading</p>
<p>&gt;	quoted</p>

The caption marker needs a block to attach to before the rule is observable at all - on its own line it is prose either way. Directly under an image, a space makes a <figure> and a tab does not.

carve
![Moon](m.jpg)
^	Figure 1
html
<p><img src="m.jpg" alt="Moon">
^	Figure 1</p>

A tab indent is the column it reaches, whatever the line holds ​

2 conformance fixtures

§24 C1 makes indentation a column claim: a space advances one column, a tab advances to the next multiple of 4. 1. claims columns 0-2, so the item's content column is 3 and a tab reaches column 4 - one column PAST it, which is what four spaces reach too. Both spellings establish the same authored block base and open the quote.

The corpus pinned neither, so two engines read the tab as if it stopped at the content column and nested a block quote no space spelling of column 4 produces (carve-js#767, carve-php#890).

carve
1. a
	> quote
html
<ol>
  <li>a
    <blockquote><p>quote</p></blockquote>
  </li>
</ol>

At the content column itself it nests, which is the boundary the rule above is drawn against.

carve
1. a
   > quote
html
<ol>
  <li>a
    <blockquote><p>quote</p></blockquote>
  </li>
</ol>

The same column, written with four spaces ​

1 conformance fixture

The control for the rule above, and the half that was stated rather than checked. That pair pins the tab against the THREE-space spelling. What makes the tab case decidable is the other comparison: a tab reaches column 4, four spaces reach column 4, so the two are the same claim and must get the same answer.

Without this document an engine can pass both halves of that pair while still answering column 4 differently depending on which whitespace arrived there - which is the exact shape of the defect the rule was written for (carve-js#767, carve-php#890).

carve
1. a
    > quote
html
<ol>
  <li>a
    <blockquote><p>quote</p></blockquote>
  </li>
</ol>

Trailing whitespace after a block marker ​

6 conformance fixtures

A block marker is what it is regardless of whitespace after it. Every engine already reads it that way, and no corpus document carried any of these six shapes - so an engine that dropped one of those tolerances could not be caught here. carve-php shipped exactly that for the continuation marker (carve#871).

A thematic break:

carve
a

--- 

b
html
<p>a</p>
<hr>
<p>b</p>

A code fence's closer:

carve
``` 
x
``` 

y
html
<pre><code>x
</code></pre>
<p>y</p>

A colon fence's closer:

carve
::: note
x
::: 

y
html
<aside class="admonition note" aria-label="Note">
  <p>x</p>
</aside>
<p>y</p>

A table's continuation row:

carve
| a | b |
+ c | d |
html
<table>
  <tbody>
    <tr><td>a c</td><td>b d</td></tr>
  </tbody>
</table>

A footnote definition separated by more than one space:

carve
[^f]:   note

see[^f]
html
<p>see<a id="fnref1" href="#fn1" role="doc-noteref"><sup>1</sup></a></p>
<section role="doc-endnotes" aria-label="Footnotes">
  <hr>
  <ol>
    <li id="fn1">
      <p>note<a href="#fnref1" role="doc-backlink" aria-label="Back to reference">↩</a></p>
    </li>
  </ol>
</section>

And the continuation marker, which §17 L3 spells "a line whose only content is +":

carve
- a
+ 
b

x
html
<ul>
  <li>a
    b
  </li>
</ul>
<p>x</p>

Line endings and a byte order mark ​

4 conformance fixtures

A line ends at \n, \r\n or a lone \r, and a byte order mark at the start of a document is not content. All four spellings below are the same document and produce the same output, including the same heading id - a carriage return that leaked into the text would show up there rather than only in whitespace nobody looks at.

The examples are written with ordinary newlines. The bytes are applied when the fixture is generated (::: compare crlf, cr, bom), because the example files are reviewable Markdown and are not protected from line-ending normalization the way tests/corpus/** is.

carve
# Title

a
b
html
<section id="Title">
  <h1>Title</h1>
  <p>a
b</p>
</section>
carve
# Title

a
b
html
<section id="Title">
  <h1>Title</h1>
  <p>a
b</p>
</section>
carve
# Title

a
b
html
<section id="Title">
  <h1>Title</h1>
  <p>a
b</p>
</section>
carve
# Title

a
b
html
<section id="Title">
  <h1>Title</h1>
  <p>a
b</p>
</section>

A null byte is replaced before the document is read ​

3 conformance fixtures

Every C0 control except tab, newline and carriage return is ordinary content in this language (PART 7), and the presentation targets emit them (PART 9 §29). U+0000 is the one exception: it is replaced by U+FFFD REPLACEMENT CHARACTER before the first line is read (PART 0 INPUT), so it is never content, it never reaches the AST, and no target is ever asked whether to emit it.

The example writes ␀ (U+2400 SYMBOL FOR NULL) where the byte goes, and the fixture is generated with ::: compare nul, which substitutes the real U+0000. The byte cannot be written here: git would call this whole file binary, and it is invisible in an editor besides. tests/corpus/** is protected from normalization and tests/fixture-bytes.test.mjs pins the byte's presence, so the generated .crv is where it is safe to keep.

carve
a␀b
html
<p>a�b</p>

EVERY occurrence, and BEFORE anything is captured verbatim. A code span carries its content exactly as authored, and this transform still reaches it - because it runs at the parse boundary rather than in a renderer, no later stage ever holds the character.

carve
`a␀b`
html
<p><code>a�b</code></p>

The control, so the carve-out is not read as covering the class: the vertical tab is a C0 control too, it is NOT carved out, and it survives into the output as content. Only U+0000 moves.

carve
cd
html
<p>cd</p>

A tab separates two attributes, and pads a block, as a space does ​

3 conformance fixtures

The heading is the name this category was given when it pinned the opposite answer, and it is kept deliberately. Corpus category names are an append-only contract - every engine allowlists them as NN-slug, so renaming one invalidates all of those lists at once. The documents below now pin the NARROWED answer (markup-carve/carve#906): a tab does not separate two attributes inside an INLINE block. The second half of the heading is still true, and is what the ruling turned on - a tab does pad a block-attribute LINE, which the category The inline attribute interior is space-only, the attribute line is not pins directly.

Every whitespace slot of the inline block takes space. All of them sit AFTER the first non-whitespace character of their line, which is where PART 7's rule says a tab is not syntax, so the block is unrecognized and its braces show:

carve
*x*{.a	.b}

*y*{	.c}

*z*{.d	}
html
<p><strong>x</strong>{.a	.b}</p>
<p><strong>y</strong>{	.c}</p>
<p><strong>z</strong>{.d	}</p>

A tab after an UNQUOTED value ends the value - unquoted_value holds letters, digits, -, _, . and : and no whitespace at all - and then satisfies no separator either, so the whole block fails. Inside a QUOTED value it is content, as any other character is, and that half did not move:

carve
*x*{k=a	.b}

*y*{k="a	b"}
html
<p><strong>x</strong>{k=a	.b}</p>
<p><strong k="a	b">y</strong></p>

The blessed EMPTY block is a separate position rather than a use of the separator, and it has to move with it: narrow the separator alone and [x]{ tab } is still a valid empty block, so the document below would keep passing and pin nothing.

carve
[x]{	}
html
<p>[x]{	}</p>

Colon fence separator must be a space ​

10 conformance fixtures

The colon fence has ONE separator slot -- the whitespace immediately after the fence run -- and all four openers share it. It is a MARKER SEPARATOR (PART 7, MARKER SEPARATORS AND PADDING SLOTS): the token after it selects an admonition, a div, a line block or a local hard-break block, so it is spelled space and a tab does not satisfy it. A tabbed opener is an ordinary paragraph, exactly as a tab after a heading, list or definition marker already is.

The four openers are pinned separately because implementations decide them in four separate places. In carve-rs the same rule lived in four branches, and fixing the first left the other three opening; carve-js and carve-php each had their own split. One representative shape would have covered a quarter of that.

An admonition opener:

carve
:::	note
x
:::
html
<p>:::	note
x
:::</p>

The separator is a run, and the rule is about the whole run rather than its first character. Both mixed spellings are prose too -- the shape that survives a fix written as "the first character must be a space" is the one with the space first.

carve
::: 	note
x
:::
html
<p>::: 	note
x
:::</p>
carve
:::	 note
x
:::
html
<p>:::	 note
x
:::</p>

A bare [label] may sit flush against the fence (:::[First]), so this slot is OPTIONAL in the div opener. Optional is a different property from a different role: when the slot IS written, it answers the same way.

carve
:::	[First]
x
:::
html
<p>:::	[First]
x
:::</p>
carve
::: 	[First]
x
:::
html
<p>::: 	[First]
x
:::</p>

A line block:

carve
:::	|
x
:::
html
<p>:::	|
x
:::</p>
carve
::: 	|
x
:::
html
<p>::: 	|
x
:::</p>

A local hard-break block. The trailing backslash is no longer a block selector once the line is prose, so it is read as ordinary inline content and produces a hard break inside the paragraph -- which is itself the discriminator, since the recognized form would have produced a div.hardbreaks wrapper instead.

carve
:::	\
x
:::
html
<p>:::	<br>
x
:::</p>
carve
::: 	\
x
:::
html
<p>::: 	<br>
x
:::</p>

Cardinality is a separate question from the terminal, and this case is the guard against a fix that drifts the other way. space names the character, not the width: a run of more than one space still opens the block.

carve
:::  note
x
:::
html
<aside class="admonition note" aria-label="Note">
  <p>x</p>
</aside>

Colon fence metadata slots must be a space too ​

5 conformance fixtures

Once admonition_type has been read the block is decided, so the opener's "title" and [label] slots carry no recognition -- they are PADDING. They are spelled space all the same, for the other reason PART 7 gives: a tab is syntax ONLY in a line's leading indentation run, and a padding slot sits after the first non-whitespace character of its line.

This half is pinned beside the separator half deliberately. A case that pinned only the separator invites a fix that narrows the whole line, and a case that pinned only the padding invites the reverse; carve-js carried both defects at once, in opposite directions. Invalid metadata now reports a diagnostic and keeps the named container with no title or label (carve#2693).

carve
::: note	"Title"
x
:::
html
<aside class="admonition note" aria-label="Note">
  <p>x</p>
</aside>
carve
::: note 	"Title"
x
:::
html
<aside class="admonition note" aria-label="Note">
  <p>x</p>
</aside>

The [label] slot reverts independently of the "title" slot, so it carries its own pair: a fixture with a tab at both cannot tell them apart, because narrowing either one already drops the metadata.

carve
::: note "Title"	[First]
x
:::
html
<aside class="admonition note" aria-label="Note">
  <p>x</p>
</aside>
carve
::: note "Title" 	[First]
x
:::
html
<aside class="admonition note" aria-label="Note">
  <p>x</p>
</aside>

The spaced spellings are unchanged, and both slots still carry their metadata.

carve
::: note "Title" [First]
x
:::
html
<aside class="admonition note" aria-labelledby="adm-1">
  <p class="admonition-title" id="adm-1">Title</p>
  <p class="div-label">First</p>
  <p>x</p>
</aside>

Table cell padding must be a space ​

21 conformance fixtures

A table cell has a padding slot at each end -- the whitespace between the opening | and the cell content, and between the content and the closing |. Both are spelled space (grammar.ebnf delimiter_cell, header_cell, data_cell, rowspan_marker, colspan_marker). Every one of them sits after the row's opening pipe, so every one of them is INLINE, and a tab is syntax only in a line's leading indentation run (PART 7, MARKER SEPARATORS AND PADDING SLOTS).

A tab written in one of those slots is therefore not padding. It stays where it is and becomes ordinary cell content, which is a visible answer rather than a rejection: the cell keeps the tab, and a delimiter cell stops being one.

Each end is pinned separately. A padding rule is easy to implement at one end only, and a document carrying a tab at both ends cannot tell a half-fix from a whole one -- the shape carve#901 found at the admonition opener.

A data cell, tab-first at the leading slot:

carve
|	a |	b |
html
<table>
  <tbody>
    <tr><td>	a</td><td>	b</td></tr>
  </tbody>
</table>

The slot is a RUN, and the rule is about the whole run rather than its first character. Both mixed spellings keep the tab as content too -- the one that survives a fix written as "the first character must be a space" is the one with the space first.

carve
| 	a | 	b |
html
<table>
  <tbody>
    <tr><td>	a</td><td>	b</td></tr>
  </tbody>
</table>
carve
|	 a |	 b |
html
<table>
  <tbody>
    <tr><td>	 a</td><td>	 b</td></tr>
  </tbody>
</table>

The trailing slot answers the same way, and reverts independently of the leading one:

carve
| a	| b	|
html
<table>
  <tbody>
    <tr><td>a	</td><td>b	</td></tr>
  </tbody>
</table>
carve
| a 	| b 	|
html
<table>
  <tbody>
    <tr><td>a 	</td><td>b 	</td></tr>
  </tbody>
</table>

A header cell carries the same two slots, after its = marker -- and since a tab is not the space that terminates a marker run, the = is content along with it (PART 9 §5 T11):

carve
|=	h |=	i |
| 1 | 2 |
html
<table>
  <tbody>
    <tr><td>=	h</td><td>=	i</td></tr>
    <tr><td>1</td><td>2</td></tr>
  </tbody>
</table>
carve
|=	 h |=	 i |
| 1 | 2 |
html
<table>
  <tbody>
    <tr><td>=	 h</td><td>=	 i</td></tr>
    <tr><td>1</td><td>2</td></tr>
  </tbody>
</table>
carve
|= h	|= i	|
| 1 | 2 |
html
<table>
  <thead>
    <tr><th scope="col">h	</th><th scope="col">i	</th></tr>
  </thead>
  <tbody>
    <tr><td>1</td><td>2</td></tr>
  </tbody>
</table>
carve
|= h 	|= i 	|
| 1 | 2 |
html
<table>
  <thead>
    <tr><th scope="col">h 	</th><th scope="col">i 	</th></tr>
  </thead>
  <tbody>
    <tr><td>1</td><td>2</td></tr>
  </tbody>
</table>

A delimiter cell is the one slot whose failure is structural rather than textual. With a tab in the padding the cell is no longer a delimiter_cell, so the second line is not a delimiter row: no header is promoted, no alignment is assigned, and the --- run is ordinary inline content that smart typography renders as an em dash.

carve
| a | b |
|	--- |	--- |
| 1 | 2 |
html
<table>
  <tbody>
    <tr><td>a</td><td>b</td></tr>
    <tr><td>	—</td><td>	—</td></tr>
    <tr><td>1</td><td>2</td></tr>
  </tbody>
</table>
carve
| a | b |
| 	--- | 	--- |
| 1 | 2 |
html
<table>
  <tbody>
    <tr><td>a</td><td>b</td></tr>
    <tr><td>	—</td><td>	—</td></tr>
    <tr><td>1</td><td>2</td></tr>
  </tbody>
</table>
carve
| a | b |
| ---	| ---	|
| 1 | 2 |
html
<table>
  <tbody>
    <tr><td>a</td><td>b</td></tr>
    <tr><td>—	</td><td>—	</td></tr>
    <tr><td>1</td><td>2</td></tr>
  </tbody>
</table>

The two span markers are padding around a single token, so a tab beside one makes the cell ordinary content and the span does not happen:

carve
| a | b |
|	^ | c |
html
<table>
  <tbody>
    <tr><td>a</td><td>b</td></tr>
    <tr><td>	^</td><td>c</td></tr>
  </tbody>
</table>
carve
| a | b |
| c |	< |
html
<table>
  <tbody>
    <tr><td>a</td><td>b</td></tr>
    <tr><td>c</td><td>	&lt;</td></tr>
  </tbody>
</table>

A continuation row's cells are data_cells too (grammar.ebnf continuation_row), so they carry the same two slots. This is the spelling an implementation is most likely to pad in a second place, and a fix applied only to the standard row leaves it joining the tab away.

carve
| a | b |
+	x | y |
html
<table>
  <tbody>
    <tr><td>a 	x</td><td>b y</td></tr>
  </tbody>
</table>
carve
| a | b |
+ x	| y	|
html
<table>
  <tbody>
    <tr><td>a x	</td><td>b y	</td></tr>
  </tbody>
</table>
carve
| a | b |
+ x | y |
html
<table>
  <tbody>
    <tr><td>a x</td><td>b y</td></tr>
  </tbody>
</table>

The spaced spellings are unchanged, and so is a cell with more than one space -- cardinality is a separate question from the terminal. A cell with NO padding is a separate question again: an unmarked cell is unchanged, while a marked one has nothing to end its run and keeps the marker as content (T11).

carve
|=h|=  i |
|a|  b  |
html
<table>
  <tbody>
    <tr><td>=h</td><th scope="row">i</th></tr>
    <tr><td>a</td><td>b</td></tr>
  </tbody>
</table>
carve
| a | b |
| --- | ---: |
| 1 | 2 |
html
<table>
  <thead>
    <tr><th scope="col">a</th><th scope="col" style="text-align: right;">b</th></tr>
  </thead>
  <tbody>
    <tr><td>1</td><td style="text-align: right;">2</td></tr>
  </tbody>
</table>
carve
| a | b |
| ^ | c |
html
<table>
  <tbody>
    <tr><td rowspan="2">a</td><td>b</td></tr>
    <tr><td>c</td></tr>
  </tbody>
</table>
carve
| a | b |
| c | < |
html
<table>
  <tbody>
    <tr><td>a</td><td>b</td></tr>
    <tr><td colspan="2">c</td></tr>
  </tbody>
</table>
7 conformance fixtures

The whitespace before a link or image title is a PADDING SLOT: the link is already a link once its destination is read, and the title sits inline after it. link_title spells that slot space, and image_title = link_title inherits it. A tab is syntax only in a line's leading indentation run (PART 7, MARKER SEPARATORS AND PADDING SLOTS), and this slot sits well past the first non-whitespace character of its line.

A tab written there is therefore not padding, and the inline is not a link at all: the bracket run stays literal text, with the tab where it was written.

A link title, tab-first:

carve
[t](/u	"T")
html
<p>[t](/u	“T”)</p>

The slot is a RUN, and the rule is about the whole run rather than either of its ends. Both mixed spellings keep the text literal too -- the one that survives a fix written as "the first character must be a space" is the one with the space first, and the one that survives "the last character must be a space" is the other.

carve
[t](/u 	"T")
html
<p>[t](/u 	“T”)</p>
carve
[t](/u	 "T")
html
<p>[t](/u	 “T”)</p>

An image title answers the same way. image_title = link_title is one production defined by reference, and every engine serves the link tail and the image tail from one function -- so the two agree by construction today, and nothing would notice the day one of them splits:

carve
![a](/p.png	"T")
html
<p>![a](/p.png	“T”)</p>
carve
![a](/p.png 	"T")
html
<p>![a](/p.png 	“T”)</p>
carve
![a](/p.png	 "T")
html
<p>![a](/p.png	 “T”)</p>

A single space is the titled form, unchanged:

carve
[t](/u "T")
html
<p><a href="/u" title="T">t</a></p>

Code fence metadata slots must be a space too ​

10 conformance fixtures

A fenced code block carries three of the same slots: the one before the info string (fenced_code_block, spelled [space]), and the "header" and [label] slots inside code_fence_info (spelled space+). All three sit after the fence run, which has already decided the block, so all three are padding and all three take a space.

The fallback here is the INVALID-FENCE FALLBACK the grammar already names: the opener is not a fence opener, so the run is read as an inline verbatim span in a paragraph and every character of it survives.

Each slot is pinned separately, because implementations decide them in three places and a document carrying a tab in all three cannot tell a partial fix from a whole one.

The slot before the info string, tab-first:

carve
```	js
x
```
html
<p><code>	js
x
</code></p>

Both mixed runs answer the same way, for the reason the title slots give:

carve
``` 	js
x
```
html
<p><code> 	js
x
</code></p>
carve
```	 js
x
```
html
<p><code>	 js
x
</code></p>

The "header" slot, which sits after the language token and reverts independently of the slot before it:

carve
```js	"T"
x
```
html
<p><code>js	"T"
x
</code></p>
carve
```js 	"T"
x
```
html
<p><code>js 	"T"
x
</code></p>
carve
```js	 "T"
x
```
html
<p><code>js	 "T"
x
</code></p>

And the [label] slot, which reverts independently of both:

carve
```js "T"	[L]
x
```
html
<p><code>js "T"	[L]
x
</code></p>
carve
```js "T" 	[L]
x
```
html
<p><code>js "T" 	[L]
x
</code></p>
carve
```js "T"	 [L]
x
```
html
<p><code>js "T"	 [L]
x
</code></p>

A single space in each slot is the fenced form, unchanged:

carve
```js "T" [L]
x
```
html
<pre title="T"><code class="language-js">x
</code></pre>

A tab continues a list item just as two spaces do ​

2 conformance fixtures

The tab-stop rule that lets a tab reach a marker column applies to a list item's CONTINUATION line as much as to its first. - item puts the content column at 2, and a following line indented with one tab reaches it exactly as two spaces do, so both spellings are one paragraph inside the item.

Pinned because the two spellings are decided in different places and one engine decides them differently. carve-js publishes no position for the paragraph or any of its three inlines when the continuation is a TAB, and places all four when it is two spaces or when the marker is 1.; carve-rs and carve-php place them either way and agree to the offset (carve-js#712). The HTML is identical in all three, which is why nothing in this corpus could see it - the divergence is entirely in PART 12 positions, which the pair below does not express and npm run ast:check now does.

The tab spelling:

carve
- item
	more

x
html
<ul>
  <li>item
more</li>
</ul>
<p>x</p>

The two-space spelling, which is the control: it is the same document, and the engine that drops the positions above keeps them here.

carve
- item
  more

x
html
<ul>
  <li>item
more</li>
</ul>
<p>x</p>

A blank line holds spaces and tabs and nothing else ​

3 conformance fixtures

blank_line = {whitespace}, newline (resources/grammar.ebnf, PART 1) over whitespace = ' ' | '\t' (PART 7). Two characters, and no third. Every other character that a host language's whitespace class might sweep up - a Unicode space separator, a C0 control, a zero-width character - is CONTENT, so a line holding one of them keeps the paragraph open and soft-breaks into it.

PART 0 states the U+FEFF row of that outright, under A LEADING BYTE ORDER MARK IS STRIPPED: a leading byte order mark is stripped, "ONE, and only there: a U+FEFF anywhere else is an ordinary zero-width character". The three documents below carry the characters raw; they are invisible in review, which is why tests/fixture-bytes.test.mjs pins each one by name.

The rule and its opposite, in one document: the line holding only a byte order mark is content and a continues into b, while a line holding only spaces and a line holding only a tab each end the paragraph. Covers U+FEFF, U+0020 and U+0009.

carve
a

b
  
c
	
d
html
<p>a

b</p>
<p>c</p>
<p>d</p>

The Unicode space separators are not whitespace either, nor is the other zero-width character PART 9 names alongside the byte order mark. One paragraph, nine soft breaks. Covers U+00A0, U+1680, U+2000, U+2009, U+200A, U+202F, U+205F, U+3000 and U+200B.

carve
a
 
 
 
 
 
 
 
 
​
b
html
<p>a
&nbsp;
 
 
 
 
 
 
 
​
b</p>

The C0 controls and the Unicode line and paragraph separators are the rows a regular-expression whitespace class reaches without anyone deciding it should. They are content too. Covers U+000B, U+000C, U+0085, U+2028 and U+2029.

carve
a


…




b
html
<p>a


…




b</p>
4 conformance fixtures

link_title = space, ('"' ... ) spells its padding slot as exactly ONE character, and four artifacts read it as a run: carve-js, carve-php, carve-rs and the executable spec all took the title after two spaces. carve#912 settled which side gives. The production is right and the four are lax, so a second space is no longer padding.

This is deliberately the opposite call from the one carve#905 made for the same slots. That change settled WHICH character a slot admits (a space, never a tab) and left HOW MANY alone; this one settles the cardinality, and settles it tight.

With two spaces the quoted run is not a title, so the bracket run is not a link at all and every character of the line survives as text:

carve
[t](/u  "T")
html
<p>[t](/u  “T”)</p>

image_title = link_title is one production defined by reference, so the image tail answers the same way:

carve
![a](/p.png  "T")
html
<p>![a](/p.png  “T”)</p>

The CONTROL, and the point of the ruling: a single space is still the titled form. This pair passed before carve#912 and passes after it, and it is what distinguishes narrowing the slot from breaking it.

carve
[t](/u "T")
html
<p><a href="/u" title="T">t</a></p>
carve
![a](/p.png "T")
html
<img src="/p.png" alt="a" title="T">

A code fence opener takes exactly one space ​

2 conformance fixtures

fenced_code_block = code_fence_open, [space], [code_fence_info] spells the opener slot as exactly one character. A second space reaches language_info, whose character class holds no space, so the opener matches no shape and the INVALID-FENCE FALLBACK applies: the run is an inline verbatim span in a paragraph.

The two metadata slots INSIDE code_fence_info are spelled space+ and are unaffected. The productions differ, so the cardinality differs, and carve#912 ruled only on the four slots spelled with a bare space.

carve
```  php
x = 1
```
html
<p><code>  php
x = 1
</code></p>

The CONTROL. One space is the lenient Djot spelling and stays a fenced block with its language:

carve
``` php
x = 1
```
html
<pre><code class="language-php">x = 1
</code></pre>

The canonical writer glues a code fence to its info string ​

2 conformance fixtures

fenced_code_block calls the no-space opener canonical and the reader accepts either spelling, so nothing in the source says which one the WRITER emits. PART 11 §6d does: no padding space between the fence run and the info string, and exactly one space before each metadata token inside it. The .fmt sidecar beside this pair pins the whole opener line, which is what was missing - the fmt corpus held no document whose canonical form contains an info string at all, so two engines normalized to the glued form and one to the spaced one with nothing to adjudicate between them.

The author's spacing is normalized away in all three slots at once, and the rendered block is unchanged by any of it.

carve
``` php   "src/Auth.php"    [Composer]
composer require x
```
html
<pre title="src/Auth.php"><code class="language-php">composer require x
</code></pre>

The canonical form is a fixed point. An opener already written the canonical way comes back byte for byte, which is the half a writer that merely reproduced the author's spelling would also pass - both examples are needed to tell the two apart.

carve
```php "src/Auth.php" [Composer]
composer require x
```
html
<pre title="src/Auth.php"><code class="language-php">composer require x
</code></pre>

A frontmatter opener takes exactly one space ​

2 conformance fixtures

frontmatter_open = "---", [space], [frontmatter_format] spells its slot as exactly one character too. With two, the second space reaches frontmatter_format = (letter | digit)+, which cannot match it, so the line is not a typed opener.

What is left is not a thematic break either -- a break is a dash run and nothing else -- so the line is ordinary paragraph text, the metadata lines fold into it as lazy continuation, and the closing --- is the thematic break. The opening dashes are then subject to smart typography like any other text, which is why they render as an em dash.

carve
---  yaml
title: T
---

body
html
<p>—  yaml
title: T</p>
<hr>
<p>body</p>

The CONTROL. One space is the lenient spelling and still opens frontmatter, which renders nothing:

carve
--- yaml
title: T
---

body
html
<p>body</p>

A reference definition's metadata slots take exactly one space ​

4 conformance fixtures

The definition line carries two of the four slots carve#912 narrowed: link_title before the quoted title, and [space, attributes] before the trailing attribute block. Both are padding -- the definition is already a definition at [a]: /url -- and both are spelled as exactly one space.

With two spaces the title is not a title, and the leftover quoted run is what the line then fails on: the definition is anchored at end of line (carve#911), so the whole line is an ordinary paragraph and the reference does not resolve.

carve
[a]: /u  "T"

[a][]
html
<p>[a]: /u  “T”</p>
<p>[a][]</p>

The attribute block answers the same way, and note where it does NOT go: the zero-space case is a different shape, because [a]: /u{.c} glues the braces to the destination and gives href="/u{.c}". Two spaces end the destination instead, so the block is left over and the production fails on it.

When these two documents were written under carve#912 the leftover was silently dropped and the line stayed a definition, which is the outcome PART 7 names as the one to avoid. carve#911 anchored the line, and this is the fallback the clause promises.

carve
[a]: /u  {.c}

[a][]
html
<p>[a]: /u  {.c}</p>
<p>[a][]</p>

The CONTROLS. One space carries the title, and one space carries the attributes:

carve
[a]: /u "T"

[a][]
html
<p><a href="/u" title="T">a</a></p>
carve
[a]: /u {.c}

[a][]
html
<p><a href="/u" class="c">a</a></p>

A definition marker's separator is a space, and it is a run ​

10 conformance fixtures

The three definition markers share one separator rule: the marker-to-content separator is the space terminal, U+0020, and a tab never satisfies it. What the grammar did not say is how MANY. footnote_definition and abbreviation_definition were spelled with a single space while all three engines and the executable spec consumed a run, so the productions forbade a shape nothing rejected. carve#892 corrects them to space+.

Note that this is the OPPOSITE cardinality answer from carve#912's, which held four PADDING SLOTS to exactly one space. The two are not in conflict, because they govern different positions. A padding slot sits between two tokens on a line whose construct is already fixed, and its width means nothing. A marker separator is what stands between the marker and the content it introduces, and a writer aligning definitions in a column is writing separator, not content.

Two spaces, at both markers:

carve
*[HTML]:  Hyper Text

HTML
html
<p><abbr title="Hyper Text">HTML</abbr></p>
carve
x[^f]

[^f]:  note
html
<p>x<a id="fnref1" href="#fn1" role="doc-noteref"><sup>1</sup></a></p>
<section role="doc-endnotes" aria-label="Footnotes">
  <hr>
  <ol>
    <li id="fn1">
      <p>note<a href="#fnref1" role="doc-backlink" aria-label="Back to reference">↩</a></p>
    </li>
  </ol>
</section>

The run is ASCII spaces, so the first other character is content ​

This is where the three engines disagreed, each in a different place, and where the executable spec gave a fourth answer no engine gave: it refused a footnote marker followed by any non-ASCII whitespace as a definition at all.

A NO-BREAK SPACE after the separator is content. The abbreviation expands to a string that starts with it:

carve
*[HTML]:  Hyper Text

HTML
html
<p><abbr title=" Hyper Text">HTML</abbr></p>

and the footnote is defined, with the character opening its body:

carve
x[^f]

[^f]:  note
html
<p>x<a id="fnref1" href="#fn1" role="doc-noteref"><sup>1</sup></a></p>
<section role="doc-endnotes" aria-label="Footnotes">
  <hr>
  <ol>
    <li id="fn1">
      <p>&nbsp;note<a href="#fnref1" role="doc-backlink" aria-label="Back to reference">↩</a></p>
    </li>
  </ol>
</section>

A TAB after the run is content by the same rule, and the two markers then answer differently for a reason downstream of this clause rather than in it. An abbreviation expansion is a raw string, so the tab survives into the title:

carve
*[HTML]: 	Hyper Text

HTML
html
<p><abbr title="	Hyper Text">HTML</abbr></p>

while a footnote body is parsed as blocks, where a leading tab is that body's own indentation run (PART 9 section 24 C1) rather than a character in it:

carve
x[^f]

[^f]: 	note
html
<p>x<a id="fnref1" href="#fn1" role="doc-noteref"><sup>1</sup></a></p>
<section role="doc-endnotes" aria-label="Footnotes">
  <hr>
  <ol>
    <li id="fn1">
      <p>note<a href="#fnref1" role="doc-backlink" aria-label="Back to reference">↩</a></p>
    </li>
  </ol>
</section>

The CONTROLS ​

A tab as the SEPARATOR is still not a separator. Widening the run is not widening the terminal, and this is the pair that separates the two:

carve
*[HTML]:	Hyper Text

HTML
html
<p>*[HTML]:	Hyper Text</p>
<p>HTML</p>
carve
x[^f]

[^f]:	note
html
<p>x[^f]</p>
<p>[^f]:	note</p>

And MARKER REQUIRES CONTENT still applies after the run. A marker followed by spaces and nothing else is a paragraph, because a line of whitespace is blank (PART 1). The spaces-only forms are pinned in tests/separator-role-split.test.mjs rather than here -- a trailing whitespace run in a reviewable Markdown source file is one editor save from vanishing -- and this is the version the corpus can hold, a bare marker:

carve
x[^f]

[^f]:
html
<p>x[^f]</p>
<p>[^f]:</p>

One space is unchanged, which is the form every document actually uses:

carve
*[HTML]: Hyper Text

HTML
html
<p><abbr title="Hyper Text">HTML</abbr></p>

Trailing whitespace on a content line is dropped ​

14 conformance fixtures

A whitespace run at the end of a content line does not reach the output. It is not content, and there is no shape of the language that gives it meaning: Carve's hard break is the backslash form, never two trailing spaces.

The rule is the project's rather than any one production's, and it decides more than the shape that raised it:

trailing (invisible and bad) whitespace is the one important rule we have: no such thing.

It was previously written down only for a paragraph's FINAL line, and PART 12 section 7 asserted the opposite for a line before a soft break. carve#926 settled it as general and corrected both.

A trailing space on the last line of a paragraph, which was already the stated rule:

carve
abc
html
<p>abc</p>

And on a line before a SOFT BREAK, which is the half the specification had backwards. These two documents are the same document:

carve
abc 
def
html
<p>abc
def</p>

A tab answers the same way, at both positions:

carve
abc	
def
html
<p>abc
def</p>

Every other line that carries content answers the same way. A heading, a list item, a block quote line and a definition entry:

carve
# Title 

- item 

> quoted
html
<section id="Title">
  <h1>Title</h1>
  <ul>
    <li>item</li>
  </ul>
  <blockquote><p>quoted</p></blockquote>
</section>
carve
:: term 
:  def
html
<dl>
  <dt>term</dt>
  <dd>def</dd>
</dl>

A table caption, which kept its run until carve#926 measured it:

carve
| a |
^ Cap
html
<table>
  <caption>Cap</caption>
  <tbody>
    <tr><td>a</td></tr>
  </tbody>
</table>

The run is whitespace, and nothing else is whitespace ​

The dropped run is ' ' or a tab, the same two-character terminal blank_line = {whitespace} takes. Every other character is CONTENT and survives, however invisible it looks in an editor. This one document carries a no-break space, a zero-width space, a byte order mark, an en quad, a form feed and an ideographic space, each at the end of its own line:

carve
a 
b​
c
d 
e
f
html
<p>a&nbsp;
b​
c
d 
e
f </p>

That is why U+FEFF was a red herring in the shape that raised this. In a line holding a space, a byte order mark and a space, the BOM is content and what is dropped is the trailing SPACE:

carve
html
<p></p>

Where the rule does not reach ​

Verbatim content keeps its bytes. A fenced code block's body is the block's payload, not a content line:

carve
```
abc 
```
html
<pre><code>abc 
</code></pre>

And whitespace INSIDE a construct is not trailing: it ends at the construct's delimiter rather than at the line's end. A code span, a literal inline and a table cell all keep it, and so does the run before a hard-break backslash:

carve
`x ` and !`y `
html
<p><code>x </code> and y </p>
carve
a \
b
html
<p>a <br>
b</p>

A LINE BLOCK is not an exception either, in the order that matters. Its MEDIAL GAPS rule converts an inner or trailing run of two or more columns into NBSP CONTENT first, and content is not whitespace -- so this rule never reaches it, and only the one-column case is left for it to drop:

carve
::: |
abc  
def 
:::
html
<div class="line-block">
  <p>abc&nbsp;&nbsp;<br>
def</p>
</div>

A definition term's continuation line ​

A dt written across two physical lines is one logical line assembled from two, and the second line's trailing run is dropped exactly like the first line's would be (markup-carve/carve#1289). Nothing exempts a term: the run is whitespace at the end of a content line, and the verbatim run that spans the break carries a newline rather than the dropped space.

carve
:: `a
b 
:  d
html
<dl>
  <dt><code>a
b</code></dt>
  <dd>d</dd>
</dl>

The control belongs to "where the rule does not reach" above rather than to this one: spaces INSIDE the run are the construct's content and end at its closing delimiter, so a term whose whole content is an all-space verbatim keeps them.

carve
:: `  `
:  d
html
<dl>
  <dt><code>  </code></dt>
  <dd>d</dd>
</dl>

A comment line's text is content and a block body is payload ​

5 conformance fixtures

NO TRAILING WHITESPACE (paragraph, NORMATIVE, CARVE-P2-025) reaches a %% comment line's text and stops at a %%% block comment's body. Exactly one space or tab after the %% marker is the separator; a second one is text. Neither half shows in HTML, since a comment renders nothing, so each pair carries a .fmt sidecar and the canonical writer is what reads the text back.

The trailing run goes. This case sits inside a container, which is where the engines disagreed (markup-carve/carve-rs#1951).

carve
::: note
%% keep  
text
:::
html
<aside class="admonition note" aria-label="Note">
  <p>text</p>
</aside>

One space after the marker is the separator, so the second one is text and the writer spells both back.

carve
%%  x

text
html
<p>text</p>

A no-break space is not whitespace, so it cannot be the separator and it survives. The writer supplies the separator space ahead of it.

carve
%% x

text
html
<p>text</p>

A %%% body is the block's payload, so its trailing space keeps its byte.

carve
%%%
body  
%%%

text
html
<p>text</p>

A vertical tab after %% and after ordinary text is content at the end of both lines. The comment stays hidden, and the text reaches HTML. The .fmt sidecar checks that the writer keeps both bytes.

carve
%%

text
html
<p>text</p>

A trailing comment takes a tab, a run start and its whole separator ​

13 conformance fixtures

A %% marker is separated from the text before it by whitespace, and a tab does that as a space does. The whole separating run goes with the comment, so two spaces before %% leave no trailing space behind. A %% that starts the inline run needs no separator at all, which is only spellable in a host whose run begins mid-line: a definition term, a table cell, a figure caption, a div label. A paragraph is the one host where the run starts at a line start, so the block layer's comment line covers it (PART 9 §21).

carve
a	%% hidden
html
<p>a</p>
carve
a  %% hidden
html
<p>a</p>
carve
:: a	%% hidden
: d
html
<dl>
  <dt>a</dt>
  <dd>d</dd>
</dl>
carve
:: %% hidden
: d
html
<dl>
  <dt></dt>
  <dd>d</dd>
</dl>
carve
| a	%% hidden | b |
|---|---|
| 1 | 2 |
html
<table>
  <thead>
    <tr><th scope="col">a</th><th scope="col">b</th></tr>
  </thead>
  <tbody>
    <tr><td>1</td><td>2</td></tr>
  </tbody>
</table>
carve
| %% hidden | b |
|---|---|
| 1 | 2 |
html
<table>
  <thead>
    <tr><th scope="col"></th><th scope="col">b</th></tr>
  </thead>
  <tbody>
    <tr><td>1</td><td>2</td></tr>
  </tbody>
</table>
carve
![alt](u)
^ cap	%% hidden
html
<figure>
  <img src="u" alt="alt">
  <figcaption>cap</figcaption>
</figure>
carve
![alt](u)
^ %% hidden
html
<figure>
  <img src="u" alt="alt">
  <figcaption></figcaption>
</figure>
carve
::: note [a	%% hidden]
body
:::
html
<aside class="admonition note" aria-label="Note">
  <p class="div-label">a</p>
  <p>body</p>
</aside>
carve
::: note [%% hidden]
body
:::
html
<aside class="admonition note" aria-label="Note">
  <p class="div-label"></p>
  <p>body</p>
</aside>
carve
a%%b and 50%% stay
html
<p>a%%b and 50%% stay</p>
carve
[a	%% hidden](/u)
html
<p><a href="/u">a</a></p>
carve
[%% hidden](/u)
html
<p><a href="/u"></a></p>

A tab after a fence or a frontmatter opener depends on where it sits ​

4 conformance fixtures

Two clauses meet on these two lines, and which one governs is decided by POSITION rather than by construct (markup-carve/carve#1295).

A tab BEFORE content on the opener is the marker-to-content separator, which is the space terminal and nothing else, so the construct does not open - the rule the definition, heading, list and task markers already carry. A tab at the END of the line, with nothing after it, is never that slot: it is trailing whitespace on a content line, PART 2 drops it, and what is left is the bare opener. Read that way the two clauses never overlap, so neither needs an exception written into it to protect the other.

A code fence whose info string is preceded by a tab is not a fence opener. The backtick run is then an ordinary inline verbatim run, and it reaches the end of the block:

carve
```	php
x
```
html
<p><code>	php
x
</code></p>

The same fence with the tab at the end of the line and no info string opens normally, because the tab never reaches the separator's question:

carve
```	
x
```
html
<pre><code>x
</code></pre>

The frontmatter delimiter is the same pair. Its opener may name a metadata format, so a tab before that token is the same separator and the same refusal - the line is not a delimiter, and with no frontmatter consumed the document starts with the text of those lines:

carve
---	yaml
title: x
---

body
html
<p>—	yaml
title: x</p>
<hr>
<p>body</p>

And a delimiter with a trailing tab and no format token opens frontmatter, which renders nothing:

carve
---	
title: x
---

body
html
<p>body</p>

Fenced code ​

3 conformance fixtures

Literal tabs in code content are preserved verbatim (a tab is not the same as spaces; display width is a CSS tab-size concern). Opt in to tab→space expansion with a tab-normalize extension.

carve
```
	indented with a tab
```
html
<pre><code>	indented with a tab
</code></pre>

A fenced-code delimiter sits exactly at its container's content column — at document level, that is column 0. Carve has no indented-code-block construct, so leading spaces carry no meaning to disambiguate against, and the fence is strict like every other block opener (an indented heading, thematic break, or block quote is likewise plain text). A run of backticks indented at document level is therefore an ordinary paragraph, and its delimiters fall back to inline code spans.

carve
 ```
 code
 ```
html
<p><code>
code
</code></p>

The closer is column-exact too. A closing run indented past the opener is not a delimiter but code content, which is what lets an indented line appear as sample text inside a fence. Here the two-space `` is content and the flush ``` `` closes.

carve
```
code
  ```
still code
```
html
<pre><code>code
  ```
still code
</code></pre>
1 conformance fixture

Trailing whitespace after the colon does not create a destination either, so it is still literal.

carve
[r]:
html
<p>[r]:</p>

Comments ​

2 conformance fixtures

Leading whitespace before %% does not matter: an indented line whose first non-whitespace content is %% is a line comment, exactly like one in the first column. It renders nothing and, like any block, interrupts an open paragraph.

carve
x
  %% indented comment
y
html
<p>x</p>
<p>y</p>

An indented comment-only line on its own renders nothing (it does not leave an empty paragraph).

carve
before

  %% indented comment

after
html
<p>before</p>
<p>after</p>

Comment columns and surviving list items ​

8 conformance fixtures

A below-column line comment can retain an ordinary follower. A comment at the content column clears that retention. A later below-column comment then has no prefix or claim to admit it, so the item ends before that comment. A sibling marker still starts the next item.

carve
- intro
%% c
tail
html
<ul>
  <li>intro
    tail
  </li>
</ul>
carve
- intro
  %% c
tail
html
<ul>
  <li>intro</li>
</ul>
<p>tail</p>
carve
- intro
%%%
c
%%%
  tail
html
<ul>
  <li>intro</li>
</ul>
<p>tail</p>
carve
- intro
%% c
- next
html
<ul>
  <li>intro</li>
  <li>next</li>
</ul>
carve
- intro
%% a
  %% b
tail
html
<ul>
  <li>intro</li>
</ul>
<p>tail</p>
carve
- intro
  %% a
%% b
tail
html
<ul>
  <li>intro</li>
</ul>
<p>tail</p>
carve
- intro
  %% a
%% b
  tail
html
<ul>
  <li>intro</li>
</ul>
<p>tail</p>
carve
- intro
  %% a
%%%
b
%%%
  tail
html
<ul>
  <li>intro</li>
</ul>
<p>tail</p>

Line blocks ​

1 conformance fixture

A gap written with TABS measures in columns, not characters: each tab advances to the next four-column stop, counted from where the run starts. So a tab is a medial gap or a lone space depending on where it lands - below, tab sits at column 3 so its tab crosses one column and collapses, while the two after wide cross two full stops and are kept. A LEADING tab follows the same arithmetic.

carve
::: |
tab	gap
wide		gap
	lead
:::
html
<div class="line-block">
  <p>tab gap<br>
wide&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gap<br>
&nbsp;&nbsp;&nbsp;&nbsp;lead</p>
</div>

Empty containers share one HTML body shape ​

2 conformance fixtures

PART 10 SS4 keeps one blank body line when an empty container renders. The container kind does not change that slot.

carve
:::
Visible body.
:::
html
<div>
  <p>Visible body.</p>
</div>

A comment-only body has the same shape because the comment publishes no HTML. The line-block case is the PART 9 §23 exception: its comment-only line becomes an empty verse paragraph, so the body is visible rather than empty.

carve
:::
%% hidden
%% hidden too
:::

::: custom
%% hidden
:::

::: note
%% hidden
:::

::: >
%% hidden
:::

::: |
%% hidden
:::

::: \
%% hidden
:::

::: figure
%% hidden
:::
html
<div>

</div>
<div class="custom">

</div>
<aside class="admonition note" aria-label="Note">

</aside>
<blockquote>

</blockquote>
<div class="line-block">
  <p></p>
</div>
<div class="hardbreaks">

</div>
<figure class="carve-figure-group">

</figure>

A title or label fills the container body slot ​

4 conformance fixtures

CARVE-P10-001 keeps one blank body line only where the body renders nothing, and it counts a rendered caption or div label as visible container content. A container whose sole content is its opener's title therefore takes no blank line beside it.

carve
::: note "Careful"
:::
html
<aside class="admonition note" aria-labelledby="adm-1">
  <p class="admonition-title" id="adm-1">Careful</p>
</aside>

A [label] fills the slot the same way, on a plain div and on a landmark that still draws its accessible name from the labels map because no title was written (CARVE-P9-018).

carve
::: [First]
:::

::: note [End]
:::
html
<div>
  <p class="div-label">First</p>
</div>
<aside class="admonition note" aria-label="Note">
  <p class="div-label">End</p>
</aside>

A body beside the title adds no blank line either, and a body that publishes no HTML does not bring one back: the title had already filled the slot. The minted id counts titled blocks in document order.

carve
::: note "Careful"
Visible body.
:::

::: note "Careful"
%% hidden
:::
html
<aside class="admonition note" aria-labelledby="adm-1">
  <p class="admonition-title" id="adm-1">Careful</p>
  <p>Visible body.</p>
</aside>
<aside class="admonition note" aria-labelledby="adm-2">
  <p class="admonition-title" id="adm-2">Careful</p>
</aside>

With neither token the slot is empty and the blank line stays. This is the control for the three pairs above: it is what tells a fix that removes the line under a title from one that removes the line everywhere.

carve
::: note
:::

:::
:::
html
<aside class="admonition note" aria-label="Note">

</aside>
<div>

</div>

A verbatim line keeps what sits past its fence opener, not past its container ​

4 conformance fixtures

CARVE-P11-016 measures a whitespace-only code line's residue from the FENCE OPENER's own column. A container's content column is the same number only where the fence is written flush at it, and every earlier case in this family was, so three rounds of fixes to this path left the over-indented reading standing (carve#2420).

An item's content column is 2 and the fence opens at 4, so the six-space line keeps the two columns past the opener rather than the four past the item.

carve
- item

    ```
    a
      
    b
    ```
html
<ul>
  <li>item
    <pre><code>a
  
b
</code></pre>
  </li>
</ul>

The guard against over-keeping: the same fence, and a line no wider than the opener's own column. That is an empty code line, and the item's narrower content column does not turn it into two spaces.

carve
- item

    ```
    a
    
    b
    ```
html
<ul>
  <li>item
    <pre><code>a

b
</code></pre>
  </li>
</ul>

A footnote body's content column is 4 and the fence opens at 6. This is the shape the ticket reproduces.

carve
x[^1]

[^1]: note

      ```
      a
        
      b
      ```
html
<p>x<a id="fnref1" href="#fn1" role="doc-noteref"><sup>1</sup></a></p>
<section role="doc-endnotes" aria-label="Footnotes">
  <hr>
  <ol>
    <li id="fn1">
      <p>note</p>
      <pre><code>a
  
b
</code></pre>
      <p><a href="#fnref1" role="doc-backlink" aria-label="Back to reference">↩</a></p>
    </li>
  </ol>
</section>

The footnote guard. Four of the line's columns sit inside the body and none past the opener, so the code line is empty.

carve
x[^1]

[^1]: note

      ```
      a
      
      b
      ```
html
<p>x<a id="fnref1" href="#fn1" role="doc-noteref"><sup>1</sup></a></p>
<section role="doc-endnotes" aria-label="Footnotes">
  <hr>
  <ol>
    <li id="fn1">
      <p>note</p>
      <pre><code>a

b
</code></pre>
      <p><a href="#fnref1" role="doc-backlink" aria-label="Back to reference">↩</a></p>
    </li>
  </ol>
</section>

A list marker in a raised colon container folds into its open paragraph ​

16 conformance fixtures

PART 0 S4 applies while the colon container holds an open paragraph (carve#2474). The host’s content column is 2 and the container’s authored base is 6. A marker at column 1, 2, 4, 6, or 8 folds into that paragraph. Column 0 opens a sibling item.

A blank closes the paragraph; an explicit container closer returns subsequent markers to the host. The final cases cover those boundaries and a code fence.

carve
- head

      :::
      a
- second
html
<ul>
  <li>head
    <div>
      <p>a</p>
    </div>
  </li>
  <li>second</li>
</ul>
carve
- head

      ::: note
      a
- second
html
<ul>
  <li>head
    <aside class="admonition note" aria-label="Note">
      <p>a</p>
    </aside>
  </li>
  <li>second</li>
</ul>
carve
- head

      :::
      a
 - second
html
<ul>
  <li>head
    <div>
      <p>a
- second</p>
    </div>
  </li>
</ul>
carve
- head

      ::: note
      a
 - second
html
<ul>
  <li>head
    <aside class="admonition note" aria-label="Note">
      <p>a
- second</p>
    </aside>
  </li>
</ul>
carve
- head

      :::
      a
  - second
html
<ul>
  <li>head
    <div>
      <p>a
- second</p>
    </div>
  </li>
</ul>
carve
- head

      ::: note
      a
  - second
html
<ul>
  <li>head
    <aside class="admonition note" aria-label="Note">
      <p>a
- second</p>
    </aside>
  </li>
</ul>
carve
- head

      :::
      a
    - second
html
<ul>
  <li>head
    <div>
      <p>a
- second</p>
    </div>
  </li>
</ul>
carve
- head

      ::: note
      a
    - second
html
<ul>
  <li>head
    <aside class="admonition note" aria-label="Note">
      <p>a
- second</p>
    </aside>
  </li>
</ul>
carve
- head

      :::
      a
      - second
html
<ul>
  <li>head
    <div>
      <p>a
- second</p>
    </div>
  </li>
</ul>
carve
- head

      ::: note
      a
      - second
html
<ul>
  <li>head
    <aside class="admonition note" aria-label="Note">
      <p>a
- second</p>
    </aside>
  </li>
</ul>
carve
- head

      :::
      a
        - second
html
<ul>
  <li>head
    <div>
      <p>a
- second</p>
    </div>
  </li>
</ul>
carve
- head

      ::: note
      a
        - second
html
<ul>
  <li>head
    <aside class="admonition note" aria-label="Note">
      <p>a
- second</p>
    </aside>
  </li>
</ul>
carve
- head

      :::
      a
      :::
  - second
html
<ul>
  <li>head
    <div>
      <p>a</p>
    </div>
    <ul>
      <li>second</li>
    </ul>
  </li>
</ul>
carve
- head

      :::
  - second
html
<ul>
  <li>head
    <div>
      <ul>
        <li>second</li>
      </ul>
    </div>
  </li>
</ul>
carve
- head

      :::
      ```
      :::
      ```
      a
  - second
      :::
  - third
html
<ul>
  <li>head
    <div>
      <pre><code>:::
</code></pre>
      <p>a
- second</p>
    </div>
    <ul>
      <li>third</li>
    </ul>
  </li>
</ul>
carve
- head

      :::
      a

 - second
html
<ul>
  <li>head
    <div>
      <p>a</p>
    </div>
  </li>
</ul>
<ul>
  <li>second</li>
</ul>

A comment span's closer below its host's column stays a delimiter ​

6 conformance fixtures

A container ends at a comment written below its content column, and a comment inside a span the container already holds is not one. Both delimiters pair whatever columns they sit at, so a description body, a note body and a list item all keep the closer and answer exactly as they answer the same span closed at its opener's base. A payload line that is not comment-shaped still ends the container, and an opener with no closer ahead opens nothing and leaves its payload as ordinary content.

carve
:: t
:  head

     %%%
     a
%%%
html
<dl>
  <dt>t</dt>
  <dd>head</dd>
</dl>
carve
:: t
:  head

     %%%
     a
%%%

   tail
html
<dl>
  <dt>t</dt>
  <dd>
    <p>head</p>
    <p>tail</p>
  </dd>
</dl>
carve
see[^f]

[^f]: head

  %%%
  a
%%%
html
<p>see<a id="fnref1" href="#fn1" role="doc-noteref"><sup>1</sup></a></p>
<section role="doc-endnotes" aria-label="Footnotes">
  <hr>
  <ol>
    <li id="fn1">
      <p>head<a href="#fnref1" role="doc-backlink" aria-label="Back to reference">↩</a></p>
    </li>
  </ol>
</section>
carve
- head

    %%%
    a
%%%
    %%%
    b
    %%%

  tail
html
<ul>
  <li><p>head</p>
    <p>tail</p>
  </li>
</ul>
carve
:: t
:  head

     %%%
a
%%%

   tail
html
<dl>
  <dt>t</dt>
  <dd>head</dd>
</dl>
<p>a</p>
<p>tail</p>
carve
:: t
:  head

     %%%
     a
html
<dl>
  <dt>t</dt>
  <dd>
    <p>head</p>
    <p>a</p>
  </dd>
</dl>

A fence closer below a nested item's column ends containers down to its owner ​

12 conformance fixtures

CARVE-P0-004's owner table carries no depth term, so a closing run below its fence's base selects an owner at any depth (carve#2490). The host's content column is 4, and with no container holding an open paragraph the run's own column picks the nearest surviving ancestor: column 0 and column 1 reach the document, column 2 reaches the outer item. Every container between the fence's host and that owner closes, the fence ends unterminated, and the run is classified in the owner at its own column.

CARVE-P0-014 names the deciding parameter, so the branch inverts where a container does hold an open paragraph: without the blank line the fence interrupts nothing and the run folds as text. The two fence kinds and the raw form answer alike, and %%% keeps the rule written for itself.

The last two cases are the parameter rather than the blank line: the fence has a closer, so it interrupts the paragraph under CARVE-P0-014's closer-lookahead rule, and the line below the base then finds no open paragraph and selects the document. The host keeps its fenced body at either depth, which is the rule the same clause works at one level. The control after them writes the second run inside a SIBLING item, where it is not a closer at all: the fence opens nothing and the paragraph keeps it.

carve
- a
  - b

    ```
    p
```

    tail
html
<ul>
  <li>a
    <ul>
      <li>b
        <pre><code>p
</code></pre>
      </li>
    </ul>
  </li>
</ul>
<pre><code>
    tail
</code></pre>
carve
- a
  - b

    ```
    p
 ```

    tail
html
<ul>
  <li>a
    <ul>
      <li>b
        <pre><code>p
</code></pre>
      </li>
    </ul>
  </li>
</ul>
<p><code></code></p>
<p>tail</p>
carve
- a
  - b

    ```
    p
  ```

    tail
html
<ul>
  <li>a
    <ul>
      <li>b
        <pre><code>p
</code></pre>
      </li>
    </ul>
    <pre><code>
  tail
</code></pre>
  </li>
</ul>
carve
- a
  - b
    ```
    p
```
html
<ul>
  <li>a
    <ul>
      <li>b
<code>
p
</code></li>
    </ul>
  </li>
</ul>
carve
- a
  - b
    ```
    p
 ```
html
<ul>
  <li>a
    <ul>
      <li>b
<code>
p
</code></li>
    </ul>
  </li>
</ul>
carve
- a
  - b
    - c

      ```
      p
```
html
<ul>
  <li>a
    <ul>
      <li>b
        <ul>
          <li>c
            <pre><code>p
</code></pre>
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>
<pre><code></code></pre>
carve
- a
  - b
    - c

      ```
      p
  ```

      tail
html
<ul>
  <li>a
    <ul>
      <li>b
        <ul>
          <li>c
            <pre><code>p
</code></pre>
          </li>
        </ul>
      </li>
    </ul>
    <pre><code>
    tail
</code></pre>
  </li>
</ul>
carve
- a
  - b

    ~~~
    p
 ~~~

    tail
html
<ul>
  <li>a
    <ul>
      <li>b
        <pre><code>p
</code></pre>
      </li>
    </ul>
  </li>
</ul>
<p>~~~</p>
<p>tail</p>
carve
- a
  - b

    ```=html
    <b>p</b>
```

    tail
html
<ul>
  <li>a
    <ul>
      <li>b
        <b>p</b>
      </li>
    </ul>
  </li>
</ul>
<pre><code>
    tail
</code></pre>
carve
- a
  - b
    ```
    p
x
    ```
html
<ul>
  <li>a
    <ul>
      <li>b
        <pre><code>p
</code></pre>
      </li>
    </ul>
  </li>
</ul>
<p>x
<code></code></p>
carve
- a
  - b
    - c
      ```
      p
x
      ```
html
<ul>
  <li>a
    <ul>
      <li>b
        <ul>
          <li>c
            <pre><code>p
</code></pre>
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>
<p>x
<code></code></p>
carve
- a
  - b
    ```
    p
  - c
    ```
html
<ul>
  <li>a
    <ul>
      <li>b
<code>
p</code></li>
      <li>c
<code></code></li>
    </ul>
  </li>
</ul>

A comment span's closer column does not move the item's ownership ​

13 conformance fixtures

CARVE-P9-053 recognizes a comment at every column and names both spellings: the %% line form, and the %%% fence form "whose body and closer travel with its opener". CARVE-P0-013 adds that a %%% run closes the span at any column and ends no container, because comment_block_open and comment_block_close each carry a whitespace slot of their own. The column the ownership question is asked of is therefore the OPENER's, and a closer written below the item's content column answers as the same closer written at it.

The first three cases are the definition band carve#1909 asked for and never got. A link definition in the payload is consumed at either closer column and a later reference stays literal. PART 9 §28 substitutes only where no closer follows at all, and there the definition is live and the reference resolves, which is what separates the two.

The next four are the same parameter read on the FOLLOWING line: at depth one, at depth two, and on an ordered marker whose content column is 3. The payload line is not prose, so it opens no paragraph for that follower to fold into, and the closer leaves the span where the opener left it.

The last case is the owner table doing its own work. A follower at the outer item's content column belongs to that item, which is a question about the follower's column and never about the closer's.

A span spelled on the MARKER LINE is the last four. CARVE-P0-007 makes the marker line's content the item's first block and lists - %% c / tail as ending the item, so a %%% span written there retains nothing for a below-column follower either, at every closer column. The %% control closes the group.

carve
- item
  %%%
  [r]: /url
%%%

[use][r]
html
<ul>
  <li>item</li>
</ul>
<p>[use][r]</p>
carve
- item
  %%%
  [r]: /url
  %%%

[use][r]
html
<ul>
  <li>item</li>
</ul>
<p>[use][r]</p>
carve
- item
  %%%
  [r]: /url

[use][r]
html
<ul>
  <li>item</li>
</ul>
<p><a href="/url">use</a></p>
carve
- item
  %%%
  hidden
%%%
tail
html
<ul>
  <li>item</li>
</ul>
<p>tail</p>
carve
- item
  %%%
  hidden
  %%%
tail
html
<ul>
  <li>item</li>
</ul>
<p>tail</p>
carve
- a
  - item
    %%%
    hidden
%%%
tail
html
<ul>
  <li>a
    <ul>
      <li>item</li>
    </ul>
  </li>
</ul>
<p>tail</p>
carve
- a
  - item
    %%%
    hidden
    %%%
tail
html
<ul>
  <li>a
    <ul>
      <li>item</li>
    </ul>
  </li>
</ul>
<p>tail</p>
carve
1. item
   %%%
   hidden
 %%%
tail
html
<ol>
  <li>item</li>
</ol>
<p>tail</p>
carve
- a
  - item
    %%%
    hidden
%%%
  tail
html
<ul>
  <li>a
    <ul>
      <li>item</li>
    </ul>
    tail
  </li>
</ul>
carve
- %%%
  hidden
  %%%
 tail
html
<ul>
  <li></li>
</ul>
<p>tail</p>
carve
- %%%
  hidden
%%%
 tail
html
<ul>
  <li></li>
</ul>
<p>tail</p>
carve
- %%%
  hidden
%%%
tail
html
<ul>
  <li></li>
</ul>
<p>tail</p>
carve
- %% c
 tail
html
<ul>
  <li></li>
</ul>
<p>tail</p>

A comment span opened below every content column is located there ​

8 conformance fixtures

CARVE-P9-053 names the %%% form as the one "whose body and closer travel with its opener", so the column that answers for the line after the span is the OPENER's. CARVE-P0-013 lets the closing run stand at any column and end no container, which is what makes the two halves separable at all. carve#2527 read that rule for an opener at the item's content column. An opener written below every open container's content column opens no span in the item collector, so its closer used to arrive as a fresh comment and answer with the column it was written at.

The first three cases are one document at three closer columns: below the content column, at it, and past it. A comment consumed below the content column retains the item for a noninterrupting follower, so tail stays in the item in all three.

The fourth is an ordered marker, whose content column is 3. The fifth is two levels in, where one opener sits below both content columns and the follower belongs to the inner item. The sixth widens the run to five, because a closer matches on run length and never on indentation. The seventh is the %% line form at the same column, which has always answered this way and is the spelling the fence form now agrees with.

The last case is a BLOCK OPENER rather than a follower: an unterminated code fence at the item's content column. The span leaves the paragraph as it found it, so the fence interrupts nothing and absorbs tail, which is the answer the same document with its closer one column further left already gave.

carve
- item
 %%%
 hidden
%%%
tail
html
<ul>
  <li>item
    tail
  </li>
</ul>
carve
- item
 %%%
 hidden
  %%%
tail
html
<ul>
  <li>item
    tail
  </li>
</ul>
carve
- item
 %%%
 hidden
   %%%
tail
html
<ul>
  <li>item
    tail
  </li>
</ul>
carve
1. item
  %%%
  hidden
   %%%
tail
html
<ol>
  <li>item
    tail
  </li>
</ol>
carve
- a
  - item
 %%%
 hidden
    %%%
tail
html
<ul>
  <li>a
    <ul>
      <li>item
        tail
      </li>
    </ul>
  </li>
</ul>
carve
- item
 %%%%%
 hidden
  %%%%%
tail
html
<ul>
  <li>item
    tail
  </li>
</ul>
carve
- item
 %% c
tail
html
<ul>
  <li>item
    tail
  </li>
</ul>
carve
- item
 %%%
 hidden
  %%%
  ```
tail
html
<ul>
  <li>item
    <pre><code>tail
</code></pre>
  </li>
</ul>

A nested marker comment keeps its own ownership ​

5 conformance fixtures

CARVE-P0-007 applies to a nested marker line too. The comment is the inner item's first block and leaves no paragraph open. A closer below the outer item's column cannot give that item a fresh comment's retention. The ordinary follower is a document paragraph at every closer column (carve#2526).

The first pair moves only the closer. The line-comment control has the same owner. The ordered pair checks a different content column.

carve
- a
  - %%%
    hidden
%%%
tail
html
<ul>
  <li>a
    <ul>
      <li></li>
    </ul>
  </li>
</ul>
<p>tail</p>
carve
- a
  - %%%
    hidden
    %%%
tail
html
<ul>
  <li>a
    <ul>
      <li></li>
    </ul>
  </li>
</ul>
<p>tail</p>
carve
- a
  - %% hidden
tail
html
<ul>
  <li>a
    <ul>
      <li></li>
    </ul>
  </li>
</ul>
<p>tail</p>
carve
1. a
   1. %%%
      hidden
%%%
tail
html
<ol>
  <li>a
    <ol>
      <li></li>
    </ol>
  </li>
</ol>
<p>tail</p>
carve
1. a
   1. %%%
      hidden
      %%%
tail
html
<ol>
  <li>a
    <ol>
      <li></li>
    </ol>
  </li>
</ol>
<p>tail</p>

Released under the MIT License.