{
"$comment": "Vectors for spec/markdown-strip.md. Offsets are UTF-8 BYTE offsets into `text`, half-open [byteStart, byteEnd). An implementation is conformant when it reproduces every entry byte-for-byte.",
"spec": "spec/markdown-strip.md",
"vectors": [
{
"name": "emphasis-stripped",
"why": "§3.1 markers go, text stays, no facet — atproto has no bold",
"source": "hello **world** and *more*",
"text": "hello world and more",
"facets": []
},
{
"name": "code-span",
"why": "§3.1 a code span is text, not markup",
"source": "use `npm test` now",
"text": "use npm test now",
"facets": []
},
{
"name": "link",
"why": "§3.2 label becomes text; the uri rides in the facet",
"source": "click [here](https://example.com)",
"text": "click here",
"facets": [
{
"byteStart": 6,
"byteEnd": 10,
"type": "link",
"value": "https://example.com"
}
]
},
{
"name": "link-with-emphasis-label",
"why": "§3.2 the label is stripped FIRST, then the facet spans the result",
"source": "click [**bold**](https://example.com)",
"text": "click bold",
"facets": [
{
"byteStart": 6,
"byteEnd": 10,
"type": "link",
"value": "https://example.com"
}
]
},
{
"name": "link-empty-label",
"why": "§3.2 an empty label emits the url as text",
"source": "[](https://example.com)",
"text": "https://example.com",
"facets": [
{
"byteStart": 0,
"byteEnd": 19,
"type": "link",
"value": "https://example.com"
}
]
},
{
"name": "link-non-http-refused",
"why": "§3.2 ⛔ javascript: emits the label as PLAIN TEXT, no facet",
"source": "[click](javascript:alert(1))",
"text": "click",
"facets": []
},
{
"name": "bare-url-trailing-punctuation",
"why": "§3.3 the normative URL_RE stops before trailing punctuation — the full stop stays text",
"source": "see https://x.com.",
"text": "see https://x.com.",
"facets": [
{
"byteStart": 4,
"byteEnd": 17,
"type": "link",
"value": "https://x.com"
}
]
},
{
"name": "mention-resolved",
"why": "§3.4 a mention MUST carry a resolved did in the target",
"source": "hi [@alice](did:plc:xyz)",
"text": "hi @alice",
"facets": [
{
"byteStart": 3,
"byteEnd": 9,
"type": "mention",
"value": "did:plc:xyz"
}
]
},
{
"name": "mention-bare-is-text",
"why": "§3.4 ⛔ a bare @alice is plain text — never a guessed did",
"source": "hi @alice",
"text": "hi @alice",
"facets": []
},
{
"name": "image-produces-nothing",
"why": "§3.5 images are embed, not body text",
"source": "",
"text": "",
"facets": []
},
{
"name": "image-only-strips-empty",
"why": "§3.5 a post that is only an image has empty text — valid",
"source": "",
"text": "",
"facets": []
},
{
"name": "wikilink",
"why": "§3.6 degrades to its label, no facet — no atproto/AS2 equivalent",
"source": "see [[Design Note]]",
"text": "see Design Note",
"facets": []
},
{
"name": "wikilink-labelled",
"why": "§3.6 the pipe form uses the label",
"source": "see [[Design Note|the note]]",
"text": "see the note",
"facets": []
},
{
"name": "hashtag",
"why": "§3.7 the # stays in TEXT, the facet value drops it",
"source": "a #p2p post",
"text": "a #p2p post",
"facets": [
{
"byteStart": 2,
"byteEnd": 6,
"type": "tag",
"value": "p2p"
}
]
},
{
"name": "hashtag-midword-is-text",
"why": "§3.7 a # inside a word is literal",
"source": "c#sharp",
"text": "c#sharp",
"facets": []
},
{
"name": "strikethrough",
"why": "§3.1 extension: markers go",
"source": "~~gone~~ here",
"text": "gone here",
"facets": []
},
{
"name": "footnote-dropped",
"why": "§3.9 refs and definitions have no projection target",
"source": "claim[^1]\n\n[^1]: the source",
"text": "claim",
"facets": []
},
{
"name": "hard-break",
"why": "§3.8 backslash break becomes one newline",
"source": "one\\\ntwo",
"text": "one\ntwo",
"facets": []
},
{
"name": "soft-break-is-space",
"why": "§3.8 a soft wrap is a space, not a newline",
"source": "one\ntwo",
"text": "one two",
"facets": []
},
{
"name": "multibyte-before-facet",
"why": "★ é is 2 bytes — a character-offset implementation puts this facet one byte early",
"source": "café https://x.com",
"text": "café https://x.com",
"facets": [
{
"byteStart": 6,
"byteEnd": 19,
"type": "link",
"value": "https://x.com"
}
]
},
{
"name": "emoji-before-facet",
"why": "★ each emoji is 4 bytes — the classic off-by-six for a char-offset implementation",
"source": "🎉🎉 tap [here](https://example.com)",
"text": "🎉🎉 tap here",
"facets": [
{
"byteStart": 13,
"byteEnd": 17,
"type": "link",
"value": "https://example.com"
}
]
},
{
"name": "multibyte-inside-mention",
"why": "★ the facet itself spans multi-byte characters",
"source": "héllo [@wörld](did:plc:abc)",
"text": "héllo @wörld",
"facets": [
{
"byteStart": 7,
"byteEnd": 14,
"type": "mention",
"value": "did:plc:abc"
}
]
},
{
"name": "heading",
"why": "§4.2 marker removed, block like any other",
"source": "## Title\n\nbody",
"text": "Title\n\nbody",
"facets": []
},
{
"name": "code-fence-verbatim",
"why": "§4.3 contents verbatim, no fence, no re-indent",
"source": "```js\nconst a = 1\n```",
"text": "const a = 1",
"facets": []
},
{
"name": "blockquote-no-prefix",
"why": "§4.4 no '> ' is invented — that would be a convention to re-specify",
"source": "> quoted\n> more",
"text": "quoted more",
"facets": []
},
{
"name": "bullet-list",
"why": "§4.5 a marker IS kept, or a list becomes indistinguishable from paragraphs",
"source": "- one\n- two",
"text": "• one\n• two",
"facets": []
},
{
"name": "ordered-list-renumbered",
"why": "§4.5 the RENDERED sequence, not the literal source numbers",
"source": "1. a\n1. b\n1. c",
"text": "1. a\n2. b\n3. c",
"facets": []
},
{
"name": "table",
"why": "§4.6 rows on lines, cells joined with ' | '; lossy and deliberately unclever",
"source": "| a | b |\n|---|---|\n| 1 | 2 |",
"text": "a | b\n1 | 2",
"facets": []
},
{
"name": "thematic-break-omitted",
"why": "§4.7 omitted entirely, including its separator",
"source": "one\n\n---\n\ntwo",
"text": "one\n\ntwo",
"facets": []
},
{
"name": "directive-contents-kept",
"why": "§4.8 a spoiler projects as its inner text — dropping it would misrepresent the post elsewhere",
"source": ":::spoiler\nthe ending\n:::",
"text": "the ending",
"facets": []
},
{
"name": "crlf-normalised",
"why": "§5.1 CRLF becomes LF before parsing",
"source": "one\r\n\r\ntwo",
"text": "one\n\ntwo",
"facets": []
},
{
"name": "blank-lines-collapsed",
"why": "§5.3 three or more newlines collapse to two",
"source": "one\n\n\n\ntwo",
"text": "one\n\ntwo",
"facets": []
},
{
"name": "outer-whitespace-trimmed",
"why": "§5.4 leading and trailing whitespace is removed from the whole text",
"source": "\n\n hello \n\n",
"text": "hello",
"facets": []
},
{
"name": "empty-source",
"why": "§2 total function — empty in, empty out, no throw",
"source": "",
"text": "",
"facets": []
},
{
"name": "whitespace-only-source",
"why": "§2 total function — a whitespace-only source degrades to empty, never throws",
"source": " \n\n ",
"text": "",
"facets": []
},
{
"name": "facets-sorted-by-bytestart",
"why": "§6.1 sorted ascending — required for byte-determinism",
"source": "[a](https://one.example) and [b](https://two.example)",
"text": "a and b",
"facets": [
{
"byteStart": 0,
"byteEnd": 1,
"type": "link",
"value": "https://one.example"
},
{
"byteStart": 6,
"byteEnd": 7,
"type": "link",
"value": "https://two.example"
}
]
},
{
"name": "unknown-syntax-is-literal",
"why": "§2.1 the dialect is closed, so syntax outside it is TEXT, never markup (hyper-social #43)",
"source": "==highlight== and $$x$$ and {: .cls}",
"text": "==highlight== and $$x$$ and {: .cls}",
"facets": []
},
{
"name": "single-tilde-is-literal",
"why": "§2.1 strikethrough is pinned as ~~x~~; a SINGLE tilde is outside the dialect and stays text",
"source": "~sub~ not strikethrough",
"text": "~sub~ not strikethrough",
"facets": []
},
{
"name": "unknown-directive-keeps-contents",
"why": "§4.8 the ONE exception — an unknown directive NAME still yields its contents, not literal text",
"source": ":::unknownName\nbody\n:::",
"text": "body",
"facets": []
},
{
"name": "mention-favela-target",
"why": "§3.4 a mention names the USER by identity root; the DID is resolved at PROJECTION time, not stored (identity.md §5)",
"source": "hi [@alice](favela://96b89f87)",
"text": "hi @alice",
"facets": [
{
"byteStart": 3,
"byteEnd": 9,
"type": "mention",
"value": "favela://96b89f87"
}
]
},
{
"name": "mention-did-target-legacy",
"why": "§8 forward-only — a legacy record carrying a did: target still resolves; nothing is rewritten",
"source": "hi [@alice](did:plc:xyz)",
"text": "hi @alice",
"facets": [
{
"byteStart": 3,
"byteEnd": 9,
"type": "mention",
"value": "did:plc:xyz"
}
]
},
{
"name": "facet-at-end-of-block",
"why": "§6.4 offsets map through §5 normalisation — a facet ending at a BLOCK BOUNDARY must survive it",
"source": "a #p2p\n\nmore",
"text": "a #p2p\n\nmore",
"facets": [
{
"byteStart": 2,
"byteEnd": 6,
"type": "tag",
"value": "p2p"
}
]
},
{
"name": "link-at-end-of-block",
"why": "§6.4 the same for a link facet — the normalisation index map must cover dropped characters",
"source": "see [here](https://x.com)\n\nnext",
"text": "see here\n\nnext",
"facets": [
{
"byteStart": 4,
"byteEnd": 8,
"type": "link",
"value": "https://x.com"
}
]
}
]
}