VersoWrite.·NoughtCalculate.·SkeinConnect.

Blog·14 August 2026

The Markdown score

Skein passes 647 of CommonMark's 650 conformance examples and 666 of GFM's 672. Here is the whole table, every failure named, and the rule that stops the number going down.

Every notes app says it uses Markdown. Almost none of them will tell you how much of it they actually implement, which is convenient, because the honest answer is usually “we did not measure”.

There are published test suites for this. CommonMark ships 650 machine-readable examples: a snippet of Markdown and the exact HTML it must produce. GitHub Flavored Markdown ships 672, which is CommonMark plus tables, task lists, strikethrough and autolinks. You run them, you get a number, and the number is checkable by anyone.

SuiteScore
CommonMark 0.29, 650 examples647 — 99.5%
GitHub Flavored Markdown 0.29, 672 examples666 — 99.1%

The part that matters more than the number

Skein has three places that read Markdown: the editor, reading mode and the export. Until this week each of them had its own parser, and they disagreed. A line reading    # Notes was a heading to two of them and body text to the third. A line reading ***really important***was plain text in one and a horizontal rule in the other two — which meant the words were deleted from what you saw.

That is not a Skein problem, it is the normal state of this category. Obsidian’s team have said on the record that Live Preview and Reading View are different systems, and their bug list shows the cost: list numbering that differs between modes, lists in blockquotes that render in one and not the other, intraword underscores handled two ways.

Skein now has one parser, and the editor, reading mode and the export are the same parse. There is a test that proves it: the same input goes through all three and the assertions compare them against each other. That is the claim the score exists to back up, and it is the one nobody else in this category currently makes.

Every section, both suites

Generated from the suites rather than typed, so the table cannot drift from the code. A dash means the section does not exist in that spec.

SectionCommonMarkGFM
Tabs11 / 1111 / 11
Backslash escapes13 / 1313 / 13
Entity and numeric character references16 / 1717 / 17
Precedence1 / 11 / 1
Thematic breaks19 / 1919 / 19
ATX headings18 / 1818 / 18
Setext headings27 / 2727 / 27
Indented code blocks12 / 1212 / 12
Fenced code blocks29 / 2929 / 29
HTML blocks43 / 4343 / 43
Link reference definitions28 / 2828 / 28
Paragraphs8 / 88 / 8
Blank lines1 / 11 / 1
Block quotes25 / 2525 / 25
List items48 / 4848 / 48
Lists27 / 2727 / 27
Code spans22 / 2222 / 22
Emphasis and strong emphasis131 / 131131 / 131
Links88 / 8887 / 87
Images22 / 2222 / 22
Autolinks19 / 1914 / 19
Raw HTML19 / 2120 / 20
Hard line breaks15 / 1515 / 15
Soft line breaks2 / 22 / 2
Textual content3 / 33 / 3
Tables (extension)8 / 8
Task list items (extension)2 / 2
Strikethrough (extension)2 / 2
Autolinks (extension)11 / 11
Disallowed Raw HTML (extension)0 / 1

What fails, and why

Nine examples in total, and none of them is a feature we have not got round to.

Five are autolinks, on purpose.CommonMark’s Autolinks section predates GitHub’s autolink extension and expects a bare URL to stay as text. Skein turns that extension on, because that is what GitHub, Obsidian and every app your notes came from do, and a URL that does not become a link reads as a broken app rather than as strict conformance.

One is disallowed raw HTML. The spec contradicts itself here: its HTML-blocks section wants <script> passed through untouched, its Disallowed Raw HTML section wants it neutered, and no single setting passes both. Skein leaves it through while parsing and neuters it on export, because an exported note is a file somebody opens in a browser. Off costs this one example and wins five.

Three are the parser underneath, not our rendering.An out-of-range numeric character reference, and two pathological HTML comments where cmark’s scanner is more permissive than the spec. The tree we are handed already differs, so there is nothing on our side to fix, and we would rather say that than quietly count them as ours.

The rule that stops the number going down

A score you measure once is a marketing number. So the suites run on every build, against a list of expected failures, and the list can only get shorter: an example that starts passing has to be removed from it, and an example that regresses fails the build. Compliance can only go up.

It has already caught something. Adding syntax highlighting to code blocks dropped the score by two examples, because CommonMark’s expected output for a fenced block is the escaped source and nothing else. Nobody spotted that by eye. The build failed and named the two examples, and highlighting became a thing the exporter does rather than a thing the parser does.

Every entry on the expected-failures list carries a written reason, for the same purpose. A number with no reason is a bug somebody decided to live with and did not say so.

Why this should matter to you

If you chose files-on-disk because you distrust lock-in, then the question underneath that choice is whether your files mean the same thing somewhere else. Conformance is that question with a number attached. It is why _underscore_ emphasis from iA Writer, Bear or Ulysses renders as italics in Skein and snake_case_name does not; why four-space indented code from Stack Overflow keeps its *args; and why a nested list stays nested.

None of that is a feature anyone asks for by name. You only notice it when it is missing, usually in a file you wrote somewhere else years ago.