I. Paste in a Three-Level Outline, and the Numbering Runs On from the First Number
Last week I was tidying a demo-需求清單 (a demo requirements list) and pasted in a whole outline carrying three levels of numbering, expecting the page to reproduce the look of 3. 起步、4. 第二步, "3. Getting started, 4. Second step". Instead the first line came out as 3 and everything below it was renumbered 4, 5, 6; worse, a few lines simply became 2, 3, 4, as if the 9 and 30 I had typed had never existed. I went back and read this page's hand-rolled parser (markdown-to-html.html, 1135 lines, 41623 bytes, only 1 script tag, 18 element ids, nothing loaded from outside) and worked out how it actually counts numbers. It cares about two fidelity questions I had never noticed: the starting number of an ordered list, and the id of a heading anchor.
This page writes the ordered list start number as one blunt rule: only when the first item's number is not equal to 1 does it attach a start attribute to the <ol>; when the first item is 1, it writes nothing and leaves the browser to continue from 1. I ran that rule of the Markdown to HTML page through several groups; the outputs compare as follows.
| The ordered list you type | The tag the page generates | What the browser finally renders |
|---|---|---|
3. 起步 / 4. 第二步 | <ol start="3"> | 3, 4 (the second item's 4 is never written; it rides on the sequence) |
1. 甲 / 1. 乙 | <ol> | 1, 2 (the first item is 1, so no start is kept) |
01. 甲 / 02. 乙 | <ol> | 1, 2 (parseInt eats the leading zero) |
2. 甲 / 9. 乙 / 30. 丙 | <ol start="2"> | 2, 3, 4 (9 and 30 are gone entirely) |
The last row is the key one. Within a list only the first item's number can decide start; the digits on middle rows and on the last row go neither into start nor onto the value of any <li>, so 9 and 30 simply vanish. Nothing raises an error; the page just renumbers quietly in sequence. My earlier complaint, why did 9. become 3., rests entirely on that single start rule. The leading-zero case is more hidden: 01. turns into the plain integer 1 after parseInt, the page judges it equal to 1, writes no start at all, and the two digits kept on purpose are lost too. The parenthesis marker 1) enters the same <ol>; mixed with dot markers (1. 甲 followed by 2) 乙) it is read as one list, no new list starts, and no type attribute is emitted either.
Figure 1: three numbered lines in, only start 2 survives on the way out
II. Five Ways of Indenting, One Level of Nesting
Outlines arrive with indentation, and I wanted to know whether the page restores item B hanging under item A. I tested all five ways of writing indentation, 2 spaces, 3 spaces, 4 spaces, Tab, and spaces combined with Tab, and every one of the five produced exactly 1 nested <ul>. The reason hides in the normalization step: before parsing, each Tab is replaced with 4 spaces, and then indentation is compared against the parent item's content column; any child indent that is not smaller than the parent's content start is folded into the same recursion level. So how many columns I actually left blank, the thing that separates levels elsewhere, is flattened here into equal treatment. If you truly want a line to become code rather than a list, you have to press against the margin and leave a full 4. One more distinction people mix up: swapping - 甲 for * 乙 starts a fresh list, yet inside an ordered list 1. through 2) never breaks one.
III. Heading Anchors: Chinese Stays, Full-Width Punctuation Goes, Duplicates Get Suffixes
The second fidelity question concerns the heading id. The anchor switch is on by default, and ids come from a short slug routine this page wrote itself. Feeding in the typical headings: a Chinese heading is preserved untouched; the mixed Hello 世界 turns into hello-世界, with English lowercased and spaces becoming hyphens; the decisive difference is full-width punctuation, because the full-width colon inside 標題:副標題 is dropped whole, the result reads 標題副標題, and 重要,注意 becomes 重要注意 the same way. ASCII punctuation is deleted too, and a symbol-only heading finally falls back to the catch-all word section.
How duplicates are handled put me at ease: headings that repeat take -1, -2, -3 suffixes in turn, and an earlier id is never overwritten. I took a document mixing duplicates, Chinese with English, and symbol-only titles, 7 headings in the body, and with generate-TOC switched on matched every contents href against the body id one by one; 0 of them failed to line up, because the TOC href and the heading id are products of the same slug call and must agree by construction. When a word count style tool counts headings in a long text, these suffixes change neither the heading count nor the order; they only keep ids from colliding. Comparing character counts is a different question.
IV. Table Alignment, Task Lists, the Stats Bar, and Downloaded Output
While measuring, I ran over the remaining behaviours nobody had documented. In a table separator row the colons decide alignment: ---: and :---: receive a text-align style, while the left-aligned column :--- produces no style attribute at all, and no align attribute is ever written on the page; switch the GFM extension off and the whole table falls back into a single <p>, vertical bars still sitting in the plain text. Task-list brackets accept lowercase x and uppercase X alike: with GFM on, both render as a checked, disabled input; with GFM off, [ ], [x] and [X] are just ordinary text, carrying neither a class nor an input.
The three stats columns measure incompatible things, the easiest place to misattribute. The 字符 (Characters) column shows the UTF-16 code-unit count of the source you typed, not bytes and not grapheme clusters. I checked it against one line, 你好,世界 part_01: the Characters column reads 13, its true UTF-8 length is 23 bytes, a gap of 10; once rendered into a <p>, the generated HTML counts 30 bytes. Swap in the pure full-width sample 測試:全角標點,混合。, and there are only 11 code units while the UTF-8 byte length reaches 33, a gap of 22. One Chinese character weighs as 1 code unit yet occupies 3 bytes, and mixed Chinese-English text exposes that gap most. Verifying real bytes means bringing a separate UTF-8 ruler; do not trust this Characters column.
The downloaded output carries two default values worth remembering too. The full-page HTML ships without a BOM by default: the product opens with the 4 bytes 3c21444f, which is <!DO at the head, and the 3-byte BOM is prepended only if you actively pick "UTF-8 + BOM" in the encoding dropdown. Auto-convert does not rerun per keystroke; it fires once the input pauses for 150 milliseconds. The page handles one document at a time, with no batch pasting entry. And the bundled sample document contains 2 <h1> inside its own generated page, the tool's internal heading structure, unrelated to article bodies here.
V. Three Small Issues You Cannot Walk Around
Q: Why did the numbers 9. and 30. disappear without even a warning?
It writes a start only when the first item is not equal to 1; numbers on middle rows go neither into start nor onto any li, so 9 and 30 are covered up by the sequential renumbering. The page does not raise an alarm for lost numbering of this kind, so you have to watch the number on the first line yourself.
Q: Can the Characters column in the stats bar stand in for a byte count?
No. It counts UTF-16 code units: a Chinese character or a full-width punctuation mark each weighs 1 unit, while a single emoji is counted as 2. Checking by bytes needs the UTF-8 ruler; in mixed text the gap reaches 22 or more.
Q: How many indented columns actually start a new level?
On this page the answer is: none of them. 2 spaces, 3 spaces, 4 spaces, Tab, and spaces combined with Tab all measured as exactly 1 nested level, because normalization first expands each Tab into 4 spaces and then judges against the parent item's content column. If you want hierarchy, rely on explicit list nesting inside the text rather than on counting spaces.
Looking back, on where the numbering comes from and what shape the ids take, this page follows its own self-defined, reproducible little rules instead of copying the default moves of any specification. For native-tag passthrough, the site already carries an older article that measured that boundary, and the two cover different ground.